TIdHTTP 通过代理将 HTTPS 请求作为 HTTP 发送
TIdHTTP sends HTTPS request as HTTP over proxy
我在使用 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 中:工具 - 选项 - 选中 "Capture HTTPS Connects" 并取消选中 "Decrypt HTTPS traffic"。生成证书并将其安装到系统。
将Fiddler的代理地址和端口设置为TIdHTTP
运行 程序并单击按钮。第一次单击 - 您会得到关于证书不正确的异常。但是如果你第二次点击 - 你不会得到任何异常,但你会得到完整的响应,你会在 Fiddler 中看到未加密的流量,就像你通过 HTTP 而不是 HTTPS 发送请求一样。
您可以在下图中看到第一次和第二次请求的结果。这是 Indy 组件中的错误,还是我试图错误地实施 SSL Pinning?
在第一次调用 TIdHTTP.Get()
时,OnVerifyPeer
事件看到了 Fiddler 的 SSL/TLS 证书而不是 Google 的证书,因此它拒绝了证书,并且底层套接字连接最终被关闭。
但是,在 IOHandler 的 InputBuffer
中遗留了未读数据(大约 162 字节的加密数据)。 根据设计,TIdIOHandlerStack.Connected()
方法returns只要有未读数据可用于满足读取操作,即使没有物理套接字连接也是如此。
因此,在第二次调用 TIdHTTP.Get()
期间最终发生的情况如下:
TIdHTTP
知道它正在通过代理通过 HTTPS 进行通信,并且它正在通过与先前相同的代理向同一 Google 服务器发出新的 HTTP 请求致电 TIdHTTP.Get()
。所以 TIdHTTP
检查 Connected()
看它是否仍然连接到代理,并且看到 Connected()
最初是 True,所以它决定跳过一个新的 CONNECT
请求并像通过现有代理连接发送新的 HTTP 请求一样继续。
但是,由于底层套接字已断开连接,TIdHTTP
必须与 Fiddler 建立新的套接字连接。在准备新的 HTTP 请求时,InputBuffer
被清除。 Connected()
再次检查,现在为 False,因此 TIdHTTP
与 Fiddler 建立新的套接字连接。新的套接字连接最初是未加密的(IOHandler 的 PassThrough
设置为 True)因此后续的 CONNECT
不会被加密(但这部分代码不知道 TIdHTTP
已经决定跳过 CONNECT
).
TIdHTTP
继续向 Fiddler 发送未加密的 GET
请求。
Fiddler 将其 TLS 隧道缓存一段时间,因此它将现有隧道重用到 Google,从而将未加密的 GET
按原样转发到 Google 通过 TLS 连接,然后将未加密的响应转发回 TIdHTTP
.
所以,归根结底,这里有三个问题在起作用(我已经打开了 ticket in Indy's issue tracker):
当发生需要关闭底层套接字的故障时,TIdHTTP
不会清除 InputBuffer
。一个简单的解决方法是让 TIdCustomHTTP.ConnectToHost()
方法在执行任何其他操作之前清除任何现有数据的 InputBuffer
。这样,它会在决定如何处理 CONNECT
之前看到连接真的消失了。我现在已经将此修复程序签入 Indy 的 SVN 存储库,并测试它在您的场景中是否有效。
TIdHTTP
过早地决定发送或跳过 CONNECT
,在它知道它正在对底层套接字做什么之前。这将需要对 TIdHTTP
的内部逻辑进行一些重写,因此将推迟到更高版本的 Indy。
Fiddler 正在通过先前加密的隧道来回转发未加密的数据。 TIdHTTP
.
对此无能为力
我在使用 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 中:工具 - 选项 - 选中 "Capture HTTPS Connects" 并取消选中 "Decrypt HTTPS traffic"。生成证书并将其安装到系统。
将Fiddler的代理地址和端口设置为TIdHTTP
运行 程序并单击按钮。第一次单击 - 您会得到关于证书不正确的异常。但是如果你第二次点击 - 你不会得到任何异常,但你会得到完整的响应,你会在 Fiddler 中看到未加密的流量,就像你通过 HTTP 而不是 HTTPS 发送请求一样。
您可以在下图中看到第一次和第二次请求的结果。这是 Indy 组件中的错误,还是我试图错误地实施 SSL Pinning?
在第一次调用 TIdHTTP.Get()
时,OnVerifyPeer
事件看到了 Fiddler 的 SSL/TLS 证书而不是 Google 的证书,因此它拒绝了证书,并且底层套接字连接最终被关闭。
但是,在 IOHandler 的 InputBuffer
中遗留了未读数据(大约 162 字节的加密数据)。 根据设计,TIdIOHandlerStack.Connected()
方法returns只要有未读数据可用于满足读取操作,即使没有物理套接字连接也是如此。
因此,在第二次调用 TIdHTTP.Get()
期间最终发生的情况如下:
TIdHTTP
知道它正在通过代理通过 HTTPS 进行通信,并且它正在通过与先前相同的代理向同一 Google 服务器发出新的 HTTP 请求致电TIdHTTP.Get()
。所以TIdHTTP
检查Connected()
看它是否仍然连接到代理,并且看到Connected()
最初是 True,所以它决定跳过一个新的CONNECT
请求并像通过现有代理连接发送新的 HTTP 请求一样继续。但是,由于底层套接字已断开连接,
TIdHTTP
必须与 Fiddler 建立新的套接字连接。在准备新的 HTTP 请求时,InputBuffer
被清除。Connected()
再次检查,现在为 False,因此TIdHTTP
与 Fiddler 建立新的套接字连接。新的套接字连接最初是未加密的(IOHandler 的PassThrough
设置为 True)因此后续的CONNECT
不会被加密(但这部分代码不知道TIdHTTP
已经决定跳过CONNECT
).TIdHTTP
继续向 Fiddler 发送未加密的GET
请求。Fiddler 将其 TLS 隧道缓存一段时间,因此它将现有隧道重用到 Google,从而将未加密的
GET
按原样转发到 Google 通过 TLS 连接,然后将未加密的响应转发回TIdHTTP
.
所以,归根结底,这里有三个问题在起作用(我已经打开了 ticket in Indy's issue tracker):
-
当发生需要关闭底层套接字的故障时,
TIdHTTP
不会清除InputBuffer
。一个简单的解决方法是让TIdCustomHTTP.ConnectToHost()
方法在执行任何其他操作之前清除任何现有数据的InputBuffer
。这样,它会在决定如何处理CONNECT
之前看到连接真的消失了。我现在已经将此修复程序签入 Indy 的 SVN 存储库,并测试它在您的场景中是否有效。TIdHTTP
过早地决定发送或跳过CONNECT
,在它知道它正在对底层套接字做什么之前。这将需要对TIdHTTP
的内部逻辑进行一些重写,因此将推迟到更高版本的 Indy。Fiddler 正在通过先前加密的隧道来回转发未加密的数据。
TIdHTTP
. 对此无能为力