使用TIdHTTP实施SSL证书固定存在问题。

因此,步骤如下:


将TIdHTTP,TIdSSLIOHandlerSocketOpenSSL和TIdCompressorZLib拖放到窗体上。
将TIdSSLIOHandlerSocketOpenSSL和TIdCompressorZLib分配给TIdHTTP的IOHandler和Compressor属性。
设置TIdSSLIOHandlerSocketOpenSSL:

Port = 0
DefaultPort = 0
SSLOptions.Method = sslvTLSv1_2
SSLOptions.SSLVersions = [sslvTLSv1_2]
SSLOptions.Mode = sslmClient
SSLOptions.VerifyMode = [sslvrfPeer]
SSLOptions.VerifyDepth = 0
OnVerifyPeer = SSLIOHandlerVerifyPeer

SSLIOHandlerVerifyPeer的代码:

function TForm2.SSLIOHandlerVerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean;
const
  LCGoogleCert = '98:1D:34:C4:F8:4A:F2:B7:C7:AB:77:AD:51:1C:51:4C:AD:76:ED:0D:0E:FA:C9:63:68:AF:28:69:94:60:BF:7A';
begin
  Result := Certificate.Fingerprints.SHA256AsString.Equals(LCGoogleCert);
end;

在表单上放置一个按钮:

procedure TForm2.Button1Click(Sender: TObject);
const
  LCGoogleURL = 'https://www.google.com/';
var
  s: UnicodeString;
begin
  s := HTTPSender.Get(LCGoogleURL);
end;

安装Fiddler
在Fiddler中:工具-选项-选中“捕获HTTPS连接”,然后取消选中“解密HTTPS通信”。生成证书并将其安装到系统。
将Fiddler的代理地址和端口设置为TIdHTTP。
运行程序,然后单击按钮。第一次单击-您将获得有关不正确证书的异常信息。但是,如果您第二次单击-您将不会获得任何例外,但是会得到完整的响应,并且您会在Fiddler中看到未加密的流量,就像通过HTTP而非HTTPS发送请求一样。


您可以在下面的图片中看到第一个和第二个请求的结果。那么这是Indy组件中的错误,还是我尝试错误地实现SSL固定?

delphi - TIdHTTP通过代理将HTTPS请求作为HTTP发送-LMLPHP

最佳答案

在第一次调用TIdHTTP.Get()时,OnVerifyPeer事件将看到Fiddler的SSL / TLS证书而不是Google的SSL / TLS证书,因此它拒绝该证书,并且基础套接字连接最终被关闭。

但是,IOHandler的InputBuffer中遗留有未读数据(大约162个字节的加密数据)。按照设计,即使没有物理套接字连接,只要有可用于读取操作的未读取数据,TIdIOHandlerStack.Connected()方法都将返回True。

因此,在第二次调用TIdHTTP.Get()期间发生的最终结果如下:


TIdHTTP知道它正在通过HTTPS通过代理进行通信,并且它正在通过与先前对TIdHTTP.Get()的调用相同的代理向同一Google服务器发出新的HTTP请求。因此,TIdHTTP检查Connected()以查看它是否仍连接到代理,并发现Connected()最初为True,因此它决定跳过新的CONNECT请求并继续进行处理,就像在发送现有代理连接上的新HTTP请求。
但是,由于基础套接字已断开连接,因此TIdHTTP必须与Fiddler建立新的套接字连接。在准备新的HTTP请求时,InputBuffer被清除。再次检查Connected(),现在为False,因此TIdHTTP与Fiddler建立新的套接字连接。新的套接字连接最初未加密(IOHandler的PassThrough设置为True),因此后续的CONNECT不会被加密(但是这段代码不知道TIdHTTP已经决定跳过CONNECT) 。
TIdHTTP继续将GET请求发送到Fiddler,并且未加密。
Fiddler会在一段时间内缓存其TLS隧道,因此它将现有隧道重用到Google,从而通过TLS连接将未加密的GET原样转发给Google,然后将未加密的响应转发回TIdHTTP


因此,最终这里存在三个问题(我已经打开了ticket in Indy's issue tracker):


当需要关闭基础套接字的故障发生时,TIdHTTP不会清除InputBuffer。一个简单的解决方法是在执行其他任何操作之前,使TIdCustomHTTP.ConnectToHost()方法清除所有现有数据的InputBuffer。这样一来,在决定对CONNECT进行处理之前,它会看到连接确实消失了。现在,我已经检查了对Indy的SVN存储库的此修复程序,并测试了该修复程序在您的情况下是否有效。
TIdHTTP决定过早发送或跳过CONNECT,直到它知道对底层套接字的处理方式。这将需要重新编写TIdHTTP的内部逻辑,因此将其推迟到Indy的更高版本。
Fiddler正在通过先前加密的隧道来回转发未加密的数据。 TIdHTTP中对此无能为力。

关于delphi - TIdHTTP通过代理将HTTPS请求作为HTTP发送,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50587946/

10-09 22:02