Не удается подключиться к серверу 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.