如何使用 Indy 和 OpenSSL 检索 TLS 根 CA 证书
How to retrieve the TLS root CA certificate using Indy and OpenSSL
是否可以使用 Indy 和 OpenSSL 从服务器检索 TLS 根 CA 证书?我已经在(德语)Delphi-Praxis http://www.delphipraxis.net/190534-indy-openssl-komplette-zertifikatskette-im-client-ermitteln.html 中发布了这个
但那里没有答案。
我需要检索服务器证书的整个证书链,以便将其显示给用户。我不想添加一堆受信任的 CA(并使列表保持最新),因为我将要处理的大多数服务器可能都有自签名证书或企业内部 CA。
这是访问 mail.startcom.org 时我的程序的输出。我认为在我的输出中缺少根 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.
我使用的 openSSL 版本是 1.0.2j,而 indy 版本是与 Delphi XE2 捆绑在一起的版本。使用 10.1 berlin(以及捆绑的 indy 版本)编译应用程序显示相同的行为。
这是我的示例应用程序的代码:
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.
我只是错过了一个电话还是 Indy/OpenSSL 有问题?
Is it possible to retrieve the TLS root CA certificate from a server using Indy and OpenSSL
关于 PKIX 的简短回答是,视情况而定。 PKIX is the Internet's PKI,并且有一个 IETF 工作组控制它。其他 PKI 的方式可能 运行 不同于 IETF 运行 的 PKIX。
在配置良好的服务器上,服务器将其终端实体证书和所有所需的中间 CA 证书发送到 build a path for validation. The server sends the intermediates to solve the PKI's which directory problem。服务器不发送根 CA,因为客户端必须已经拥有它。
许多服务器也会发送根 CA。这通常是毫无意义的,因为客户端必须在带外获取根并且已经信任它。如果客户端没有根,那么坏人可以简单地换入他的根和链。
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")
我好像连接不上:
$ 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
并且:
$ 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 通过 SMTPS 发送的电子邮件似乎存在配置问题。也许这就是你问题的根源。看来它们是 运行 通过 TLS 端口发送纯文本邮件。请注意下面的响应 - 它是一个纯文本邮件命令:
$ 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
如果您可以让 s_client
工作,那么它会告诉您应该使用哪个根证书。这将是链 s_client
打印中的最后一个 Issuer(有关示例,请参阅 “verify error:num=20” when connecting to gateway.sandbox.push.apple.com)。
一旦您知道根证书的可分辨名称,您就可以通过SSL_load_verify_locations
在 OpenSSL 中使用它。我现在不知道如何 Delphi.
顺便说一下,您通常也应该使用 服务器名称指示。在 OpenSSL 中,您可以使用 SSL_set_tlsext_host_name
来做到这一点。我现在不知道如何 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.
顺便说一下,您可以从 Certificates and Public Key Infrastructure.
页面下载 StartCom 证书
是否可以使用 Indy 和 OpenSSL 从服务器检索 TLS 根 CA 证书?我已经在(德语)Delphi-Praxis http://www.delphipraxis.net/190534-indy-openssl-komplette-zertifikatskette-im-client-ermitteln.html 中发布了这个 但那里没有答案。
我需要检索服务器证书的整个证书链,以便将其显示给用户。我不想添加一堆受信任的 CA(并使列表保持最新),因为我将要处理的大多数服务器可能都有自签名证书或企业内部 CA。
这是访问 mail.startcom.org 时我的程序的输出。我认为在我的输出中缺少根 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.
我使用的 openSSL 版本是 1.0.2j,而 indy 版本是与 Delphi XE2 捆绑在一起的版本。使用 10.1 berlin(以及捆绑的 indy 版本)编译应用程序显示相同的行为。
这是我的示例应用程序的代码:
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.
我只是错过了一个电话还是 Indy/OpenSSL 有问题?
Is it possible to retrieve the TLS root CA certificate from a server using Indy and OpenSSL
关于 PKIX 的简短回答是,视情况而定。 PKIX is the Internet's PKI,并且有一个 IETF 工作组控制它。其他 PKI 的方式可能 运行 不同于 IETF 运行 的 PKIX。
在配置良好的服务器上,服务器将其终端实体证书和所有所需的中间 CA 证书发送到 build a path for validation. The server sends the intermediates to solve the PKI's which directory problem。服务器不发送根 CA,因为客户端必须已经拥有它。
许多服务器也会发送根 CA。这通常是毫无意义的,因为客户端必须在带外获取根并且已经信任它。如果客户端没有根,那么坏人可以简单地换入他的根和链。
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")
我好像连接不上:
$ 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
并且:
$ 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 通过 SMTPS 发送的电子邮件似乎存在配置问题。也许这就是你问题的根源。看来它们是 运行 通过 TLS 端口发送纯文本邮件。请注意下面的响应 - 它是一个纯文本邮件命令:
$ 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
如果您可以让 s_client
工作,那么它会告诉您应该使用哪个根证书。这将是链 s_client
打印中的最后一个 Issuer(有关示例,请参阅 “verify error:num=20” when connecting to gateway.sandbox.push.apple.com)。
一旦您知道根证书的可分辨名称,您就可以通过SSL_load_verify_locations
在 OpenSSL 中使用它。我现在不知道如何 Delphi.
顺便说一下,您通常也应该使用 服务器名称指示。在 OpenSSL 中,您可以使用 SSL_set_tlsext_host_name
来做到这一点。我现在不知道如何 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.
顺便说一下,您可以从 Certificates and Public Key Infrastructure.
页面下载 StartCom 证书