Ufx
Ufx

Reputation: 2695

How to fix OpenSSL incorrect data transferring?

I need to transfer serialized data using OpenSSL. My code works but for some cases transferred data has some differences.

This is serialized data http://s000.tinyupload.com/?file_id=02935614701824936895

int sslWrite(SSL *ssl, const void *buf, int num)
{
    const int MAX_SIZE = 0x10000;

    const int INT_SIZE = sizeof(int);
    const int numv = INT_SIZE + num;

    if (numv > MAX_SIZE) {
        return -1;
    }

    std::vector<char> bufv(numv);

    memcpy(&bufv[0], &numv, INT_SIZE);
    if (num != 0) {
        memcpy(&bufv[INT_SIZE], buf, num);
    }

    int n = 0;
    while (n < num) {
        int w = SSL_write(ssl, &bufv[0], numv - n);
        if (w < 0) {
            return 0;
        }
        n += w;
    }

    return n;
}

int sslRead(SSL *ssl, void *buf, int num)
{
    const int INT_SIZE = sizeof(int);
    const int BUF_SIZE = 0x10000;
    char bufv[BUF_SIZE];

    int m = SSL_read(ssl, (char *)bufv, num);

    if (m < 0) {
        return m;
    }

    const int n = *(int *)(bufv);

    if (n > BUF_SIZE - INT_SIZE) {
        return -1;
    }

    while (m < n) {
        int k = SSL_read(ssl, (char *)bufv + m, num - m);
        if (k < 0) {
            return k;
        }
        m += k;
    }

    memcpy(buf, &bufv[INT_SIZE], n - INT_SIZE);
    return n - INT_SIZE;
}

-

// Server
#include <fstream>
#include <WS2tcpip.h>
#include <Winsock2.h>
#define _WINSOCKAPI_
#include <windows.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/applink.c>

int OpenListener(int port)
{
    int sd;
    struct sockaddr_in addr;
    sd = socket(PF_INET, SOCK_STREAM, 0);
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
        perror("can't bind port");
        abort();
    }
    if (listen(sd, 10) != 0) {
        perror("Can't configure listening port");
        abort();
    }
    return sd;
}

SSL_CTX* InitServerCTX(void)
{
    const SSL_METHOD *method;
    SSL_CTX *ctx;
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    method = SSLv2_server_method();
    ctx = SSL_CTX_new(method);
    if (ctx == NULL) {
        ERR_print_errors_fp(stderr);
        abort();
    }
    return ctx;
}

void LoadCertificates(SSL_CTX* ctx, char* CertFile, char* KeyFile)
{
    if (SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        abort();
    }
    if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        abort();
    }
    if (!SSL_CTX_check_private_key(ctx)) {
        fprintf(stderr, "Private key does not match the public certificate\n");
        abort();
    }
}

int main()
{
    SSL_CTX *ctx;
    int server;

    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    SSL_library_init();

    ctx = InitServerCTX();
    LoadCertificates(ctx, "sert.crt", "sert.key");
    server = OpenListener(3456);

    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);

    int client = accept(server, (struct sockaddr*)&addr, &len);

    SSL *ssl;
    printf("Connection: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
    ssl = SSL_new(ctx);
    SSL_set_fd(ssl, client);

    if (SSL_accept(ssl) == -1) {
        ERR_print_errors_fp(stderr);
    }
    else {
        std::ifstream fin("data", std::ios::binary);
         std::string data((std::istreambuf_iterator<char>(fin)), std::istreambuf_iterator<char>());
        fin.close();

        sslWrite(ssl, &data[0], (int)data.size());

        const int BUF_SIZE = 0x10000;
        std::string data1;
        data1.resize(BUF_SIZE);
        int n = sslRead(ssl, &data1[0], BUF_SIZE);
        data1.resize(n);

        /////////////////////////////////////////
        bool b = (data == data1); // b - FALSE!!!
        /////////////////////////////////////////
    }

    SSL_free(ssl);
    closesocket(client);
}

-

// Client
#include <string>
#include <WS2tcpip.h>
#include <Winsock2.h>
#define _WINSOCKAPI_
#include <windows.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/applink.c>

SSL_CTX *InitCTX(void)
{
    const SSL_METHOD *method;
    SSL_CTX *ctx;
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    method = SSLv2_client_method();
    ctx = SSL_CTX_new(method);
    if (ctx == NULL) {
        ERR_print_errors_fp(stderr);
        abort();
    }
    return ctx;
}

int OpenConnection(const char *hostName, int port)
{
    struct hostent *host;
    struct sockaddr_in addr;
    if ((host = gethostbyname(hostName)) == NULL) {
        perror(hostName);
        abort();
    }
    int sd = socket(PF_INET, SOCK_STREAM, 0);
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = *(long*)(host->h_addr);
    if (connect(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
        closesocket(sd);
        perror(hostName);
        abort();
    }
    return sd;
}

int main()
{
    const std::string &hostName = "localhost";
    const int port = 3456;

    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    SSL_library_init();

    SSL_CTX *ctx = InitCTX();
    int server = OpenConnection(hostName.c_str(), port);
    SSL *ssl = SSL_new(ctx);
    SSL_set_fd(ssl, server);
    if (SSL_connect(ssl) == -1) {
        ERR_print_errors_fp(stderr);
    }
    else {
        const int BUF_SIZE = 0x10000;
        std::string data;
        data.resize(BUF_SIZE);
        int n = sslRead(ssl, &data[0], BUF_SIZE);
        data.resize(n);

        sslWrite(ssl, &data[0], (int)data.size());
    }
}

Upvotes: 1

Views: 613

Answers (1)

janm
janm

Reputation: 18339

You must deal with partial writes and partial reads.

For SSL_write() you need to check the return value to make sure your data was completely written. If your data was only partially written you need to call SSL_write() again for the remaining data. Repeat until all your data is written.

For SSL_read() you need to keep reading until you get a zero return value, then check for a clean shutdown. While doing this you need to append to your buffer and build it up as you receive data.

Update:

@dascandy in comments is correct.

You need to know when to stop reading as well as dealing with partial reads. Closing the connection alone will not work in your case because when you close the connection with SSL you lose the ability to send the reply on the same connection. SSL does not have "half open" connections like TCP.

A reasonable approach is to prefix each message with a byte count when sending, and when receiving first read the byte count (using whatever encoding is convenient -- fixed length, text with delimiter, whatever) and then read exactly that number of bytes into your buffer.

Upvotes: 2

Related Questions