2014-02-14 79 views
2

我正尝试构建通过Indy SSL TCP组件(C++ Builder 2010)进行通信的服务器和客户端应用程序。我已经生成的证书和私钥用下面的命令:无法连接到Indy SSL TCP服务器

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -config C:\openssl.cnf 

Server代码:

#include <vcl.h> 
#pragma hdrstop 

#include <tchar.h> 
//--------------------------------------------------------------------------- 

#pragma argsused 

#include <idglobal.hpp> 
#include <IdTcpServer.hpp> 
#include <IdSSLOpenSSL.hpp> 
#include <vector> 
#include <string> 
#include <memory> 
#include <iostream> 
#include <windows.h> 
#include "comm.h" 

#pragma link "IndyCore140.lib" 
#pragma link "IndyProtocols140.lib" 
#pragma link "IndySystem140.lib" 

///////////////////////////////////////////////////////////////////////////// 
// Server 
///////////////////////////////////////////////////////////////////////////// 
class TServer 
{ 
public: 
    TServer(int Port, const std::string& cert, const std::string& key, 
      const std::string& password) 
    : FPassword(password), 
     FServer(new TIdTCPServer(NULL)) 
    { 

     FServer->OnConnect = ServerOnConnect; 
     FServer->OnExecute = ServerOnExecute; 
     FServer->DefaultPort = Port; 
     TIdServerIOHandlerSSLOpenSSL* ioHandler = 
      new TIdServerIOHandlerSSLOpenSSL(NULL); 
     ioHandler->OnGetPassword = SetPassword; 
     ioHandler->OnVerifyPeer = VerifyPeer; 
     ioHandler->SSLOptions->Mode = sslmServer; 
     ioHandler->SSLOptions->VerifyDepth = 0; 
     ioHandler->SSLOptions->CertFile = cert.c_str(); 
     ioHandler->SSLOptions->KeyFile = key.c_str(); 
     ioHandler->SSLOptions->SSLVersions << sslvSSLv23; 
     ioHandler->SSLOptions->VerifyMode.Clear(); 
     FServer->IOHandler = ioHandler; 
    } 
    ~TServer() 
    { 
    } 
public: 
    void Start() 
    { 
     FServer->Active = true; 
     std::cout << "Listening on port " << FServer->DefaultPort << std::endl; 
    } 
    void Stop() 
    { 
     FServer->Active = false; 
    } 

private: 
    void __fastcall ServerOnExecute(TIdContext* ctx) 
    { 
     TIdTCPConnection* conn = ctx->Connection; 
     try 
     { 
      std::string command = Recv(conn); 
      std::cout << command << std::endl; 
      if(strnicmp(command.c_str(), "HELLO", 5) == 0) // Start session 
      { 
       Send(conn, "HELLO"); 
      } 
     } 
     catch(Exception& e) 
     { 
      std::cout << AnsiString(e.Message).c_str() << std::endl; 
     } 
     conn->Disconnect(); 
    } 
    void __fastcall ServerOnConnect(TIdContext* context) 
    { 
     std::cout << "Client connected" << std::endl; 
    } 
    bool __fastcall VerifyPeer(TIdX509* Certificate, bool AOk, int ADepth) 
    { 
     return AOk; 
    } 
    void __fastcall SetPassword(AnsiString& Password) 
    { 
     Password = FPassword.c_str(); 
    } 

private: 
    std::auto_ptr<TIdTCPServer> FServer; 
    const std::string FPassword; 
}; 

/// 
// Press Ctrl+C to close application 
/// 
HANDLE hExitEvent = NULL; 

BOOL CtrlHandler(DWORD ctl) 
{ 
    if(ctl == CTRL_C_EVENT) 
    { 
     if(hExitEvent != NULL) 
     { 
      std::cout << "Closing application..." << std::endl; 
      SetEvent(hExitEvent); 
     } 
     return TRUE; 
    } 
    return FALSE; 
} 
/// 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
    std::auto_ptr<TServer> server(new TServer(50136, 
     "c:\\cert.pem", 
     "c:\\key.pem", 
     "MyPassword")); 
    hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 
    if(SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE)) 
    { 
     try 
     { 
      server->Start(); 
      WaitForSingleObject(hExitEvent, INFINITE); 
      server->Stop(); 
      Sleep(1000); 
     } 
     catch(Exception& e) 
     { 
      std::cout << AnsiString(e.Message).c_str() << std::endl; 
     } 
    } 
    CloseHandle(hExitEvent); 
    return 0; 
} 

客户端代码:

#include <vcl.h> 
#pragma hdrstop 
#include <idglobal.hpp> 
#include <IdTCPClient.hpp> 
#include <IdSSLOpenSSL.hpp> 
#include <vector> 
#include <string> 
#include <memory> 
#include <iostream> 
#include <tchar.h> 
#include "comm.h" 
//--------------------------------------------------------------------------- 

#pragma argsused 

#pragma link "IndyCore140.lib" 
#pragma link "IndyProtocols140.lib" 
#pragma link "IndySystem140.lib" 

void TestConnection() 
{ 
    std::auto_ptr<TIdTCPClient> client(new TIdTCPClient(NULL)); 
    try 
    { 
     client->Host = "192.168.1.3"; 
     client->Port = 50136; 
     client->ConnectTimeout = 10000; 
     client->ReadTimeout = 10000; 
     // SSL 
     TIdSSLIOHandlerSocketOpenSSL* ioHandler = new TIdSSLIOHandlerSocketOpenSSL(NULL); 
     ioHandler->SSLOptions->Mode = sslmClient; 
     ioHandler->SSLOptions->VerifyDepth = 0; 
//  ioHandler->SSLOptions->CertFile = "c:\\cert.pem"; 
     ioHandler->SSLOptions->SSLVersions << sslvSSLv23; 
//  ioHandler->SSLOptions->VerifyMode.Clear(); 
     client->IOHandler = ioHandler; 

     client->Connect(); 
     /// 
     // Test session start 
     /// 
     Send(client.get(), "HELLO"); 
     std::string response = Recv(client.get()); 
     std::cout << response << std::endl; 
    } 
    catch(Exception& e) 
    { 
     std::cout << AnsiString(e.Message).c_str() << std::endl; 
    } 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    TestConnection(); 
    return 0; 
} 

comm.h

#ifndef COMM_H 
#define COMM_H 

#include <idglobal.hpp> 
#include <IdTcpServer.hpp> 
#include <IdSSLOpenSSL.hpp> 
#include <vector> 
#include <string> 
//--------------------------------------------------------------------------- 

typedef std::vector<unsigned char> TBuffer; 

void SendByteArray(TIdTCPConnection* Connection, 
    const TBuffer& array) 
{ 
    TIdBytes src; 
    src = Idglobal::RawToBytes(&array[0], array.size()); 
    Connection->IOHandler->Write(src); 
} 
//--------------------------------------------------------------------------- 
void ReceiveByteArray(TIdTCPConnection* Connection, 
    TBuffer& array, unsigned int size) 
{ 
    TIdBytes dest; 
    Connection->IOHandler->ReadBytes(dest, size); 
    array.resize(size); 
    Idglobal::BytesToRaw(dest, &array[0], size); 
} 

void Send(TIdTCPConnection* Connection, const std::string& cmd) 
{ 
    TBuffer buffer(cmd.begin(), cmd.end()); 
    SendByteArray(Connection, buffer); 
} 

std::string Recv(TIdTCPConnection* Connection) 
{ 
    TBuffer buffer; 
    ReceiveByteArray(Connection, buffer, 5); 
    std::string cmd(buffer.begin(), buffer.end()); 
    return cmd; 
} 

#endif //COMM_H 

的服务器启动没有任何错误。当我尝试连接到服务器的客户端抛出异常

Project sslclient.exe raised exception class EIdOSSLConnectError with message 'Error connecting with SSL. 
EOF was observed that violates the protocol'. 

,服务器进入与在每次迭代例外Connection Closed Gracefully.无限循环。我使用OpenSSL库v.1.0.1.3在Windows 7上运行测试。请帮助使其工作。

回答

3

客户端错误是因为当TIdServerIOHandlerSSLOpenSSL接受新的客户端连接时,客户端的IOHandler的PassThrough属性默认设置为true,因此SSL/TLS尚未激活。这允许服务器根据每个连接动态决定是否激活SSL/TLS。例如,如果您的服务器正在侦听多个端口并且仅在某些端口上使用SSL。或者如果您的协议实现STARTTLS样式命令。所以,你需要设置PassThrough属性设置为false,当你准备好接受SSL/TLS握手,例如:

void __fastcall ServerOnConnect(TIdContext* context) 
{ 
    std::cout << "Client connected" << std::endl; 
    static_cast<TIdSSLIOHandlerSocketOpenSSL*>(context->Connection->IOHandler)->PassThrough = false; 
} 

在客户端,集PassThrough为false,当你准备发起一个SSL/TLS握手:

ioHandler->PassThrough = false; 

如果PassThrough是假的时Connect()被调用时,握手会立即一旦插座成功地连接到服务器执行。因此,Indy依赖于例外,所以你不应该在OnExecute事件处理程序中使用try/catch块。您可以使用OnException事件记录未捕获的异常,如:

void __fastcall ServerOnExecute(TIdContext* ctx) 
{ 
    TIdTCPConnection* conn = ctx->Connection; 

    std::string command = Recv(conn); 
    std::cout << command << std::endl; 
    if(strnicmp(command.c_str(), "HELLO", 5) == 0) // Start session 
    { 
     Send(conn, "HELLO"); 
    } 

    conn->Disconnect(); 
} 

void __fastcall ServerOnException(TIdContext* ctx, Exception *excpt) 
{ 
    std::cout << AnsiString(excpt->Message).c_str() << std::endl; 
} 

但是,如果你必须使用一个try/catch然后一定要重新抛出任何EIdException基于异常你赶上。让TIdTCPServer处理它们,例如:

void __fastcall ServerOnExecute(TIdContext* ctx) 
{ 
    TIdTCPConnection* conn = ctx->Connection; 
    try 
    { 
     std::string command = Recv(conn); 
     std::cout << command << std::endl; 
     if(strnicmp(command.c_str(), "HELLO", 5) == 0) // Start session 
     { 
      Send(conn, "HELLO"); 
     } 
    } 
    catch(const Exception& e) 
    { 
     std::cout << AnsiString(e.Message).c_str() << std::endl; 
     if (dynamic_cast<const EIdException*>(&e)) 
      throw; 
    } 
    conn->Disconnect(); 
} 

或者:

void __fastcall ServerOnExecute(TIdContext* ctx) 
{ 
    TIdTCPConnection* conn = ctx->Connection; 
    try 
    { 
     std::string command = Recv(conn); 
     std::cout << command << std::endl; 
     if(strnicmp(command.c_str(), "HELLO", 5) == 0) // Start session 
     { 
      Send(conn, "HELLO"); 
     } 
    } 
    catch(const EIdException&) 
    { 
     throw; 
    } 
    catch(const Exception& e) 
    { 
     std::cout << AnsiString(e.Message).c_str() << std::endl; 
    } 
    conn->Disconnect(); 
} 

而且,这些线路也错了:

ioHandler->SSLOptions->SSLVersions << sslvSSLv23; 
ioHandler->SSLOptions->VerifyMode.Clear(); 

您不能使用<<操作上财产,并致电Clear()是没有任何操作。原因是因为两行都调用属性获取器,然后操作未分配回属性的临时对象。你必须是手工做的:

ioHandler->SSLOptions->SSLVersions = TIdSSLVersions() << sslvSSLv23; 
ioHandler->SSLOptions->VerifyMode = TIdSSLVerifyModeSet(); 

最后一点:

#pragma link "IndyCore140.lib" 
#pragma link "IndyProtocols140.lib" 
#pragma link "IndySystem140.lib" 

你不应该直接链接到Indy的.lib文件。您的项目的.bpr/.cproj文件,而不是您的代码,应该包含对Indy的运行时包的引用。

+0

谢谢。你的回答是我的问题的确切解决方案。我只纠正'ioHandler-> SSLOptions-> VerifyMode = TIdSSLVerifyModeSet();' –