cwebsocketopensslclientcryptocurrency

My c websocket implementation is not working when trying to connect to Deribit


Mainly for fun purposes, I've written a really simple c application that implements a websocket trying to connect with Deribit, via SSL. The application connects with Deribit (working properly), does the handshake (working properly), send a message (maybe working) and then receive a message (not working).

The program is the following:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define PRINTERR() fprintf(stderr, "%s:L%i: error\n", __FILE__, __LINE__)

int main()
{
    /* connection*/
    const char *host = "www.deribit.com";
    const char *path = "/ws/api/v2";
    int port = 443;

    struct sockaddr_in server_addr;
    struct hostent *server;

    // Create socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        PRINTERR();
        return -1;
    }

    // Configure server address
    server = gethostbyname(host);
    if (server == NULL) {
        PRINTERR();
        return -1;
    }

    bzero((char *) &server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr,
          (char *)&server_addr.sin_addr.s_addr,
          server->h_length);
    server_addr.sin_port = htons(port);

    // Connect to server
    if (connect(sockfd,(struct sockaddr *) &server_addr,sizeof(server_addr)) < 0) {
        PRINTERR();
        return -1;
    }

    // Initialize SSL context
    SSL_CTX *ssl_context;
    SSL *ssl;
    SSL_library_init();
    SSL_load_error_strings();
    ssl_context = SSL_CTX_new(TLS_client_method());
    if (!ssl_context) {
        PRINTERR();
        return -1;
    }

    SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, NULL);

    // NOTE: assuming Ubuntu
    if (SSL_CTX_load_verify_locations(ssl_context, "/etc/ssl/certs/ca-certificates.crt", NULL) != 1) {
        PRINTERR();
        return -1;
    }


    // Connect SSL over the socket
    ssl = SSL_new(ssl_context);
    SSL_set_fd(ssl, sockfd);

    int ret = SSL_connect(ssl);
    if (ret != 1) {
        PRINTERR();
        return -1;
    } else {
        long ret = SSL_get_verify_result(ssl);
        if (ret != X509_V_OK) {
            PRINTERR();
            return -1;
        }
    }

    printf("connected to deribit (sockfd: %i)\n", sockfd);

    /* handshake */
    char request[1024];
    int request_len;
    char response[1024];
    int response_len;

    // Prepare the WebSocket handshake request
    request_len = sprintf(request, "GET %s HTTP/1.1\r\n"
                                   "Host: %s\r\n"
                                   "Upgrade: websocket\r\n"
                                   "Connection: Upgrade\r\n"
                                   "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
                                   "Sec-WebSocket-Version: 13\r\n\r\n",
                                   path, host);
    // Send the WebSocket handshake request
    if (SSL_write(ssl, request, request_len) < 0) {
        PRINTERR();
        return -1;
    }

    // Receive the WebSocket handshake response
    response_len = SSL_read(ssl, response, sizeof(response));
    if (response_len < 0) {
        PRINTERR();
        return -1;
    }

    // Check if the connection has been upgraded to a WebSocket connection
    if (strstr(response, "HTTP/1.1 101 Switching Protocols") &&
        strstr(response, "upgrade: websocket") &&
        strstr(response, "Connection: upgrade")) {
            printf("WebSocket handshake successful\n");
    } else {
        printf("WebSocket handshake failed\n");
        return -1;
    }

    /* send message */
    char req[1024];
    snprintf(
        req,
        sizeof(req),
        "{\"jsonrpc\":\"2.0\",\"id\":8212,\"method\":\"public/test\",\"params\":{}}"
    );

    int message_length = strlen(req);
    int sent_bytes = 0;
    char frame[10 + message_length];
    int frame_length = 2 + message_length;

    // Set the frame header
    frame[0] = 0x81; // Fin + Text opcode
    if (message_length <= 125)
    {
        frame[1] = (char) message_length;
        frame_length = 2 + message_length;
    }
    else if (message_length <= 65535)
    {
        frame[1] = 126;
        frame[2] = (char) (message_length >> 8);
        frame[3] = (char) (message_length & 0xFF);
        frame_length = 4 + message_length;
    }
    else
    {
        frame[1] = 127;
        frame[2] = (char) (message_length >> 56);
        frame[3] = (char) (message_length >> 48);
        frame[4] = (char) (message_length >> 40);
        frame[5] = (char) (message_length >> 32);
        frame[6] = (char) (message_length >> 24);
        frame[7] = (char) (message_length >> 16);
        frame[8] = (char) (message_length >> 8);
        frame[9] = (char) (message_length & 0xFF);
        frame_length = 10 + message_length;
    }

    // Copy the message into the frame
    memcpy(frame + 2, req, message_length);

    // Send the frame over SSL
    sent_bytes = SSL_write(ssl, frame, frame_length);

    /* receive message */
    unsigned char buf[4096];
    unsigned char mask[4];
    int bytes_received;
    int payload_length;
    unsigned char frame_header[2];
    bytes_received = SSL_read(ssl, frame_header, 2);

    if (bytes_received < 0) {
        PRINTERR();
        return -1;
    }


    if (frame_header[0] == 0x88) {
        printf("connection closed by deribit\n");
    }

    payload_length = (frame_header[1] & 0x7F);
    if (payload_length == 126) {
        bytes_received = SSL_read(ssl, &payload_length, 2);
        if (bytes_received < 0) {
            PRINTERR();
            return -1;
        }
    } else if (payload_length == 127) {
        bytes_received = SSL_read(ssl, &payload_length, 8);
        if (bytes_received < 0) {
            PRINTERR();
            return -1;
        }
    }

   if (frame_header[1] & 0x80) {
        bytes_received = SSL_read(ssl, mask, 4);
        if (bytes_received < 0) {
            PRINTERR();
            return -1;
        }
    }

    bytes_received = SSL_read(ssl, buf, payload_length);
    if (bytes_received < 0) {
        PRINTERR();
        return -1;
    }
    if (frame_header[1] & 0x80) {
        //unmask_message(buf, mask, bytes_received);
    }

    printf("frame header received: [0]: %d, [1]: %d\n", frame_header[0], frame_header[1]);
    printf("payload_length: %i\n", (frame_header[1] & 0x7F));
    printf("mask:           %i\n", (frame_header[1] & 0x80));
    printf("buffer: %s\n", buf);

    return 0;
}

If you run the code, you'll see that right after the first message sent (whatever the content of the message) the app receives from deribit the connection close message (frame_header[1] & 0x80 is true). Where does the problem come from? From the dealing with SSL? Or is there something wrong with the websocket implementation?

I tried using the directly the sockfd without using SSL. I tried to check potential inconsistency between my certificates and those accepted by Deribit. I tried different messages to send to Deribit.


Solution

  • 10 months late, but I just solved this exact issue on one of my own projects and thought I'd share for any future Googlers.

    Since this application is acting as the "client" to the Deribit server, it needs to mask it's payloads. As per RFC 6455, this requires 4 bytes in the frame header (immediately following the length byte(s)) to store the masking key, as well as the MSB of the frame's 2nd byte to be set. Note that only 7 bits of the frame's 2nd byte are used to store payload length. The first bit (MSB) is always used to indicate whether the payload is masked or not, and whether to expect 4 more bytes in the frame header for the masking key.

    Then the payload data must be masked using the masking key as described in RFC 6455:

    Octet i of the transformed data ("transformed-octet-i") is the XOR of octet i of the original data ("original-octet-i") with octet at index i modulo 4 of the masking key ("masking-key-octet-j"):

    j = i MOD 4 transformed-octet-i = original-octet-i XOR masking-key-octet-j

    A possible implementation in code:

    int _mask = 0x12345678;  // Should be randomly generated (securely)
    char* mask_bytes = (char*)&_mask;
    for (int i = 0; i < message_length; i++) {
        *((char*)frame + 6 + i) ^= mask_bytes[i % 4];  // frame + 6 is the beginning of the payload
    }
    

    I got the original code to communicate correctly with the server by:

    1. Setting the mask bit (frame[1] |= 0x80;)
    2. Adding 4 bytes for the mask in the frame header and copying a random 4-byte value into them
    3. Using this random 4-byte value to mask the payload as described above (I pasted the code snippet that I used)

    Recap: Frames sent from the client must be masked, and frames sent from the server must NOT be masked. OP forgot to mask the data sent from the client.