Delphi TIdHTTP通过代理以HTTP的形式发送HTTPS请求

Delphi TIdHTTP通过代理以HTTP的形式发送HTTPS请求,delphi,fiddler,indy,delphi-xe8,idhttp,Delphi,Fiddler,Indy,Delphi Xe8,Idhttp,我在使用TIdHTTP实现SSL证书固定时遇到问题 因此,以下是步骤: 在表单上删除TIdHTTP、TIdSSLIOHandlerSocketOpenSSL和TIdCompressorZLib 将TIdSSLIOHandlerSocketOpenSSL和TIdCompressorZLib分配给TIdHTTP的IOHandler和Compressor属性 安装程序TIdSSLIOHandlerSocketOpenSSL: Port = 0 DefaultPort = 0 SSLOptions.Me

我在使用
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:Tools-Options中,选中“捕获HTTPS连接”并取消选中“解密HTTPS流量”。生成证书并将其安装到系统

  • 将Fiddler的代理地址和端口设置为TIdHTTP

  • 运行程序并单击按钮。第一次单击-您将获得有关错误证书的异常。但是如果你第二次点击——你不会得到任何异常,但是你会得到完整的响应,你会在Fiddler中看到未加密的流量,就像你通过HTTP而不是HTTPS发送请求一样

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


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

    但是,IOHandler的
    InputBuffer
    (大约162字节的加密数据)中仍有未读数据。根据设计,
    TIdIOHandlerStack.Connected()
    方法返回True,只要存在可满足读取操作的未读数据,即使没有物理套接字连接

    因此,在第二次调用
    TIdHTTP.Get()
    时,结果如下:

    • TIdHTTP
      知道它正在通过代理通过HTTPS进行通信,并且它正在通过与先前调用
      TIdHTTP.Get()
      相同的代理向同一个Google服务器发出新的HTTP请求。因此
      TIdHTTP
      检查
      Connected()
      以查看它是否仍然连接到代理,并查看
      Connected()
      最初为真,因此它决定跳过一个新的
      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

    因此,归根结底,这里有三个问题(我打开了一个窗口):

    • 当发生需要关闭基础套接字的故障时,
      TIdHTTP
      不会清除
      InputBuffer
      。一个简单的解决方法是在执行任何其他操作之前,让
      TIdCustomHTTP.ConnectToHost()
      方法清除任何现有数据的
      InputBuffer
      。这样,在决定如何处理
      CONNECT
      之前,它会看到连接确实消失了。现在,我已将此修复程序签入Indy的SVN存储库,并在您的场景中进行了测试

    • TIdHTTP
      在它知道它正在对底层套接字做什么之前,过早地决定发送或跳过
      CONNECT
      。这需要重新编写
      TIdHTTP
      的内部逻辑,因此将推迟到Indy的更高版本

    • Fiddler正在通过先前加密的隧道来回转发未加密的数据。在
      TIdHTTP
      中对此无能为力

    否。解密已关闭(请参阅第7页)。如果Fiddler解密HTTPS通信,则在协议列中显示“HTTPS”。这里,第一行来自失败的请求,第二行来自第二个请求,该请求也应该失败,但通过HTTP而不是HTTPS发送。这些步骤也可以在最新版本的Indy上重现。以下是https解密请求: