Search code examples
delphiopensslindyindy10

How to retrieve the TLS root CA certificate using Indy and OpenSSL


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?


Solution

  • 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.