我在使用TIdHTTP
实施SSL证书固定时遇到了问题。
所以,以下是步骤:
设置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 Pinning?
答案 0 :(得分:2)
在第一次拨打TIdHTTP.Get()
时,OnVerifyPeer
活动会看到Fiddler的SSL / TLS证书而非Google,因此拒绝了证书和底层套接字连接最终被关闭。
但是,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
不会被加密(但这部分代码不知道{{1}已经决定跳过TIdHTTP
)。
CONNECT
继续向Fiddler发送TIdHTTP
请求,未加密。
Fiddler将其TLS隧道缓存一段时间,因此它会重用现有隧道到Google,从而通过TLS连接将未加密的GET
原样转发给Google,然后转发未加密的回复回GET
。
所以,最终,这里有三个问题(我打开了一个ticket in Indy's issue tracker):
TIdHTTP
未清除TIdHTTP
。一个简单的解决方法是在执行任何其他操作之前使InputBuffer
方法清除任何现有数据的TIdCustomHTTP.ConnectToHost()
。这样,在决定如何处理InputBuffer
之前,它确实看到连接已经消失。我现在已经将此修复程序检入了Indy的SVN存储库,并测试它在您的方案中是否有效。
CONNECT
决定在它知道底层套接字正在做什么之前,过早发送或跳过TIdHTTP
。这将需要重新编写CONNECT
的内部逻辑,因此它将推迟到Indy的更高版本。
Fiddler通过先前加密的隧道来回转发未加密的数据。在TIdHTTP
。