Не удается подключиться к серверу Indy SSL TCP

Я пытаюсь создать сервер и клиентские приложения, которые взаимодействуют через компоненты TCP TCP Indy (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 ответ

Решение

Ошибка клиента, потому что когда TIdServerIOHandlerSSLOpenSSL принимает новое клиентское соединение, PassThrough свойство IOHandler клиента по умолчанию установлено в 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 опирается на исключения, поэтому вы не должны использовать try/catch блок в OnExecute обработчик события. Вы можете использовать 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"

Вы не должны ссылаться на файлы.lib Indy напрямую. Файл.bpr/.cproj вашего проекта, а не ваш код, должен содержать ссылки на исполняемые пакеты Indy.

Другие вопросы по тегам