TIdHTTPProxyServer SSL支持

时间:2013-07-16 13:16:04

标签: delphi https indy proxy-server

我在delphi xe2上使用indy 10.5.8开发代理服务器 如您所知TIdHTTPProxyServer不支持ssl 所以我添加了一个ssl io处理程序,并且ssl握手没问题但是在该服务器返回错误“套接字错误#10054连接由同行重置”。并断开连接 ssl libs没问题,我只更改了onbefor命令:

if (TIdTCPClient(AContext.OutboundClient).Port = 443) then
begin
  if not (AContext.OutboundClient.IOHandler is TIdSSLIOHandlerSocketOpenSSL) then
    begin
      if Assigned(AContext.OutboundClient.IOHandler) then
        AContext.OutboundClient.IOHandler.Free;
      AContext.OutboundClient.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(AContext.OutboundClient);
    end;
  SSLClient := TIdSSLIOHandlerSocketOpenSSL(AContext.OutboundClient.IOHandler);
  SSLClient.SSLOptions.Method     := sslvSSLv23;
  SSLClient.SSLOptions.Mode       := sslmClient;
  SSLClient.SSLOptions.SSLVersions:= [sslvSSLv2, sslvSSLv23, sslvSSLv3, sslvTLSv1];
  SSLClient.SSLOptions.VerifyMode := [];
  SSLClient.OnStatus              := StausChange;
  SSLClient.OnStatusInfo          := StausChangeex;
  SSLClient.PassThrough           := False;
  AContext.OutboundClient.IOHandler.ReadTimeout := 5000;
end else if AContext.OutboundClient.IOHandler is TIdSSLIOHandlerSocketOpenSSL then
begin
  TIdSSLIOHandlerSocketOpenSSL(AContext.OutboundClient.IOHandler).PassThrough := true;
end;

并且跟踪日志是:

Resolving hostname accounts.google.com.
Connecting to 173.194.70.84.
SSL status: "before/connect initialization"
SSL status: "before/connect initialization"
SSL status: "SSLv2/v3 write client hello A"
SSL status: "SSLv3 read server hello A"
SSL status: "SSLv3 read server certificate A"
SSL status: "SSLv3 read server key exchange A"
SSL status: "SSLv3 read server done A"
SSL status: "SSLv3 write client key exchange A"
SSL status: "SSLv3 write change cipher spec A"
SSL status: "SSLv3 write finished A"
SSL status: "SSLv3 flush data"
SSL status: "SSLv3 read finished A"
SSL status: "SSL negotiation finished successfully"
SSL status: "SSL negotiation finished successfully"
Cipher: name = ECDHE-RSA-AES128-GCM-SHA256; description = ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(128) Mac=AEAD
; bits = 128; version = TLSv1/SSLv3; 
SSL status: "SSL negotiation finished successfully"
Disconnected.
Socket Error # 10054
Connection reset by peer.

1 个答案:

答案 0 :(得分:0)

如果客户端使用HTTP CONNECT命令,则代理不应该使用SSL本身,或者根本不对其进行操作。它唯一的工作是在客户端和目标服务器之间建立一个原始套接字连接,然后在它们之间来回传递原始数据,仅此而已。对于SSL,这是非常重要,因为代理必须首先建立原始连接,然后客户端直接与目标服务器握手,而不是与代理服务器,否则客户端无法验证服务器的证书保护连接。这是TIdHTTPProxyServer默认情况下的行为,因此不要尝试在这种情况下手动激活SSL!

如果客户端使用HTTP GETPOSTHEAD命令,则客户端不直接与目标服务器通信。客户端指定要连接的代理的完整URL,然后TIdHTTPProxyServer充当客户端的HTTP服务器,并充当其自身的HTTP客户端到目标服务器。在这种情况下,客户端必须直接与TIdHTTPProxyServer握手。在这种情况下需要注意的一个问题是,由于指定了完整的URL,因此URL中指定的协议(HTTP与HTTPS)隐含了端口号。因此,除非客户端连接到非标准端口,否则TIdTCPClient(AContext.OutboundClient).Port的可能性通常为80甚至是HTTPS,因为TIdHTTPProxyServer目前在连接到目标之前不区分HTTP和HTTPS URL服务器

您可以使用TIdHTTPProxyServerContext.Command属性来了解客户端是否正在使用CONNECT命令。

您必须手动处理端口问题,直到TIdHTTProxyServer可以修复。

尝试更像这样的东西:

procedure TForm1.IdHTTPProxyServer1Connect(AContext: TIdContext);
begin
  if AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase then begin
    TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough := (AContext.Connection.Socket.Binding.Port <> 443);
  end;
end;

procedure TForm1.IdHTTPProxyServer1HTTPBeforeCommand(AContext: TIdHTTPProxyServerContext);
var
  LURI: TIdURI;
  SSLClient: TIdSSLIOHandlerSocketOpenSSL;
begin
  if TextIsSame(AContext.Command, 'CONNECT') then begin
    Exit; // let TIdHTTPProxyServer do its normal behavior...
  end;

  LURI := TIdURI.Create(AContext.Target);
  try
    if not TextIsSame(LURI.Protocol, 'https') then begin
      Exit; // let TIdHTTPProxyServer do its normal behavior...
    end;

    // Port 80 is TIdHTTPProxyServer's default Port when
    // the requested URL does not explicitly specify a
    // port, so update the correct port if needed...
    if (TIdTCPClient(AContext.OutboundClient).Port = 80) and (LURI.Port = '') then begin
      TIdTCPClient(AContext.OutboundClient).Port := 443;
    end;
  finally
    LURI.Free;
  end;

  // The AContext.OutboundClient does not have a default
  // IOHandler assigned yet at this stage...
  SSLClient := TIdSSLIOHandlerSocketOpenSSL.Create(AContext.OutboundClient);
  SSLClient.SSLOptions.Mode       := sslmClient;
  SSLClient.SSLOptions.SSLVersions:= [sslvSSLv2, sslvSSLv3, sslvTLSv1];
  SSLClient.SSLOptions.VerifyMode := [];
  SSLClient.OnStatus              := StausChange;
  SSLClient.OnStatusInfo          := StausChangeex;
  SSLClient.PassThrough           := False;
  SSLClient.ReadTimeout           := 5000;

  AContext.OutboundClient.IOHandler := SSLClient; 
end;