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?
Is it possible to retrieve the TLS root CA certificate from a server using Indy and OpenSSL
The short answer with respect to PKIX is, it depends. PKIX is the Internet's PKI, and there's an IETF working group that controls it. Other PKI's may be run differently than the way the IETF runs PKIX.
On a well configured server, the server sends its end-entity certificate and all intermediate CA certificates required to build a path for validation. The server sends the intermediates to solve the PKI's which directory problem. The server does not send the Root CA because the client must already have it.
Many servers will send the Root CA too. This is usually kind of pointless because the client must get the root out-of-band and already trust it. If the client does not have the root, then a bad guy can simply swap-in his root and chain.
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")
I can't seem to connect:
$ openssl s_client -starttls smtp -connect mail.startcom.org:25 -servername mail.startcom.org -tls1
140735103578496:error:0200203C:system library:connect:Operation timed out:crypto/bio/b_sock2.c:108:
140735103578496:error:2008A067:BIO routines:BIO_connect:connect error:crypto/bio/b_sock2.c:109:
connect:errno=60
And:
$ openssl s_client -connect mail.startcom.org:465 -servername mail.startcom.org -tls1
CONNECTED(00000003)
140735103578496:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:ssl/record/ssl3_record.c:250:
...
StartCom appears to have a configuration problem with their email over SMTPS. Maybe that's the source of your problems. It appears they are running plain text mail over the TLS ports. Notice the response below - its a plain text mail command:
$ openssl s_client -connect mail.startcom.org:465 -servername mail.startcom.org -tls1 -debug
CONNECTED(00000003)
write to 0x7f8d1241fd70 [0x7f8d12821600] (128 bytes => 128 (0x80))
0000 - 16 03 01 00 7b 01 00 00-77 03 01 cc f5 46 b3 e3 ....{...w....F..
0010 - ef 84 0b b4 ca 05 7a 6b-e7 6f 9b d7 15 aa 80 c3 ......zk.o......
0020 - e5 4f 3d a5 76 56 1d 63-dc c1 3d 00 00 12 c0 0a .O=.vV.c..=.....
0030 - c0 14 00 39 c0 09 c0 13-00 33 00 35 00 2f 00 ff ...9.....3.5./..
0040 - 01 00 00 3c 00 00 00 16-00 14 00 00 11 6d 61 69 ...<.........mai
0050 - 6c 2e 73 74 61 72 74 63-6f 6d 2e 6f 72 67 00 0b l.startcom.org..
0060 - 00 04 03 00 01 02 00 0a-00 0a 00 08 00 1d 00 17 ................
0070 - 00 19 00 18 00 23 00 00-00 16 00 00 00 17 .....#........
0080 - <SPACES/NULS>
read from 0x7f8d1241fd70 [0x7f8d12819203] (5 bytes => 5 (0x5))
0000 - 32 32 30 20 6d 220 m
If you can get s_client
to work, then it will tell you which root certificate you should use. It will be the last Issuer in the chain s_client
prints (for an example, see “verify error:num=20” when connecting to gateway.sandbox.push.apple.com).
Once you know the root certificate's Distinguished Name, then you use it in OpenSSL via SSL_load_verify_locations
. I don't now how to do it in Delphi.
By the way, you should usually use Server Name Indication, too. In OpenSSL, you do that with SSL_set_tlsext_host_name
. I don't now how to do it in Delphi.
Once you know the root certificate's Distinguished Name, then you use it in OpenSSL via
SSL_load_verify_locations
. I don't now how to do it in Delphi.
By the way, you can download the StartCom certificates from the page Certificates and Public Key Infrastructure.