is it possible to retrieve the TLS root CA certificate from a server using Indy and OpenSSL? I already posted this in the (German) Delphi-Praxis http://www.delphipraxis.net/190534-indy-openssl-komplette-zertifikatskette-im-client-ermitteln.html but got no answer there.
I need to retrieve the whole certificate chain of the server's certificate in order to display it to the user. I do not want to add a bunch of trusted CAs (and keep the list up to date) as probably most of the servers I will deal with will either have self-signed certificates or enterprise internal CAs.
This is the output of my program when accessing mail.startcom.org. I think in my output there is missing the root CA ("/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority")
*** Got certificate. Depth = 1, Error = 20
Subject: /C=IL/O=StartCom Ltd./OU=StartCom Certification Authority/CN=StartCom Class 3 OV Server CA
Issuer: /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
SHA1 Fingerprint: 5A:F7:D2:E0:80:5E:1C:7B:E5:74:22:F3:5E:63:6B:25:94:47:E5:D7
*** Got certificate. Depth = 0, Error = 20
Subject: /C=IL/ST=HaDarom/L=Eilat/O=StartCom Ltd. (Start Commercial Limited)/CN=mail.startcom.org
Issuer: /C=IL/O=StartCom Ltd./OU=StartCom Certification Authority/CN=StartCom Class 3 OV Server CA
SHA1 Fingerprint: F2:65:56:CD:A7:41:73:D8:FE:B6:85:4F:D8:79:E4:BA:3F:4D:78:C7
EIdConnClosedGracefully: Connection Closed Gracefully.
Th openSSL version I used is 1.0.2j and the indy version is the one bundled with Delphi XE2. Compiling the application with 10.1 berlin (and the indy version bundled with) shows the same behaviour.
Here is the code of my sample application:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
IdSmtp, IdSSLOpenSSL, IdExplicitTLSClientServerBase;
type
TForm1 = class(TForm)
btnGetCertChain: TButton;
Memo1: TMemo;
chkCancelTlsHandshake: TCheckBox;
editHost: TEdit;
editPort: TEdit;
procedure btnGetCertChainClick(Sender: TObject);
function TlsVerifyPeer(Certificate: TIdX509;
AOk: Boolean; ADepth, AError: Integer): Boolean;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FSMTPClient: TIdSMTP;
procedure InitTls();
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnGetCertChainClick(Sender: TObject);
begin
memo1.Clear();
Memo1.Lines.Add('Trying to connect to ' + editHost.Text + '.' + editPort.Text);
FSMTPClient.Host := editHost.Text;//'mail.startcom.org';
FSmtpClient.Port := StrToIntDef(editPort.Text, -1);//25;
try
FSmtpClient.Connect();
FSMTPClient.Authenticate(); // calls StartTLS
except
on E: Exception do
begin
// when we "cancel" the handshake, there will be an exception...
Memo1.Lines.Add(E.ClassName + ': ' + E.Message);
end;
end;
FSmtpClient.Disconnect();
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FSMTPClient := TIdSMTP.Create(nil);
InitTls();
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FSMTPClient);
end;
procedure TForm1.InitTls;
var
SslIoHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
SslIoHandler := TIdSSLIOHandlerSocketOpenSSL.Create(FSMTPClient);
SslIoHandler.SSLOptions.Method := sslvTLSv1;
SslIoHandler.SSLOptions.VerifyMode := [sslvrfPeer];
SslIoHandler.SSLOptions.VerifyDepth := 9; // 9 is default: https://linux.die.net/man/3/ssl_ctx_set_verify_depth
// SslIoHandler.SSLOptions.RootCertFile ; // don't have one
SslIoHandler.OnVerifyPeer := TlsVerifyPeer; // Necessary for certificate verification
FSMTPClient.IOHandler := SslIoHandler; // ownership of SslIoHandler is moved
FSMTPClient.UseTLS := utUseRequireTLS;
end;
function TForm1.TlsVerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth,
AError: Integer): Boolean;
begin
// store/output certificate info... or just output for demonstrational purpose
Memo1.Lines.Add('');
Memo1.Lines.Add('*** Got certificate. Depth = ' + inttostr(ADepth)+', Error = ' + IntToStr(AError));
Memo1.Lines.Add(' Subject: ' + Certificate.Subject.OneLine);
Memo1.Lines.Add(' Issuer: ' + Certificate.Issuer.OneLine);
Memo1.Lines.Add(' SHA1 Fingerprint: ' + Certificate.Fingerprints.SHA1AsString);
result := true;
if chkCancelTlsHandshake.Checked then
begin
// for now we do not want to continue - present certificates to the user first
result := ADepth > 0; // false for leaf cert; true for (intermediate) CAs
end;
end;
end.
Am I just missing a call or is this a problem with Indy/OpenSSL?