无法连接到 Indy SSL TCP 服务器

3

我正在尝试构建通过Indy SSL TCP组件通信的服务器和客户端应用程序(C++ Builder 2010)。我使用以下命令生成了证书和私钥:

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

服务器代码:

#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. 的无限循环。我在Windows 7上使用OpenSSL库v.1.0.1.3运行测试。请帮助解决问题。

1个回答

4
客户端错误是因为当 TIdServerIOHandlerSSLOpenSSL 接受新的客户端连接时,客户端 IOHandler 的 PassThrough 属性默认设置为 true ,因此 SSL/TLS 尚未激活。这使得服务器可以根据每个连接动态决定是否激活 SSL/TLS,例如如果您的服务器在多个端口上侦听并且仅在某些端口上使用 SSL,则可以这样做。或者如果您的协议实现了类似于 STARTTLS 命令。因此,在准备接受 SSL/TLS 握手时,您需要将 PassThrough 属性设置为 false,例如:
void __fastcall ServerOnConnect(TIdContext* context)
{
    std::cout << "Client connected" << std::endl;
    static_cast<TIdSSLIOHandlerSocketOpenSSL*>(context->Connection->IOHandler)->PassThrough = false;
}

在客户端,当您准备好启动SSL/TLS握手时,请将PassThrough设置为false:
ioHandler->PassThrough = false;

如果在调用Connect()PassThrough为false,则一旦套接字成功连接到服务器,握手将立即执行。
话虽如此,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()是无效的。原因是这两行代码都会调用属性getter方法并且操作了临时对象,但是没有将修改过后的值重新赋给属性。你需要手动完成这个过程:

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运行时包的引用。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接