Question

I am trying to build the server and the client applications that communicate via Indy SSL TCP components (C++ Builder 2010). I've generated certificate and private key with following command:

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

Server code:

#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;
}

Client code:

#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

The server starts without any error. When I try to connect to the server the client throws exception

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

and the server enters to the infinite loop with exception Connection Closed Gracefully. at each iteration. I run tests on Windows 7 with OpenSSL libraries v.1.0.1.3. Please help to make it work.

Was it helpful?

Solution

The client error is because when TIdServerIOHandlerSSLOpenSSL accepts a new client connection, the PassThrough property of the client's IOHandler is set to true by default, thus SSL/TLS is not active yet. This allows the server to decide dynamically, on a per-connection basis, whether to activate SSL/TLS or not. Such as if your server is listening on multiple ports and only uses SSL on certain ports. Or if your protocol implements a STARTTLS style command. So you need to set the PassThrough property to false when you are ready to accept the SSL/TLS handshake, eg:

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

On the client side, set PassThrough to false when you are ready to initiate a SSL/TLS handshake:

ioHandler->PassThrough = false;

If PassThrough is false when Connect() is called, the handshake will be performed immediately once the socket successfully connects to the server.

With that said, Indy relies on exceptions, so you should not use a try/catch block in the OnExecute event handler. You can use the OnException event to log uncaught exceptions, eg:

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;
}

But, if you must use a try/catch then be sure to re-throw any EIdException based exceptions you catch. Let TIdTCPServer handle them, eg:

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();
}

Or:

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();
}

Also, these lines are also wrong:

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

You cannot use the << operator on a property, and calling Clear() is a no-op. The reason is because both lines are invoking the property getters and then manipulating temp objects that are not assigned back to the properties afterwards. You have to do that manually:

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

And lastly:

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

You should not be linking to Indy's .lib files directly. Your project's .bpr/.cproj file, not your code, should contain references to Indy's runtime packages instead.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top