1

I'm dealing with code written by a 3rd party (which I don't want to radically change as I'll explain) which opens a tcp connection to talk json-rpc with a server. I want to add ssl to this as the server has an ssl port for the same comms. EDIT: I want to avoid using external proxies (like stunnel, which works).

Currently, the socket is opened by socket() and passed to curl via CURLOPT_OPENSOCKETFUNCTION and libcurl is told to stop after connecting (perform no data transfer) via CURLOPT_CONNECT_ONLY.

I tried enabling the SSL options in CURL starting with curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL); but they had no effect, i.e. the connection itself succeeded in that the function connect() returned true (CURL was happy) but the data is still being sent unencrypted.

My understanding is: the socket is created externally (and passed to CURL which is told to not handle data transfer) and then written to directly, not via CURL or OpenSSL, so nothing actually encrypts the transfer ... despite enabling SSL in CURL.

I also tried to point CURL to https:// by doing sprintf(sctx->curl_url, "https%s", strstr(url, "://")); and then I get:

SSL: couldn't create a context: error:140A90A1:lib(20):func(169):reason(161)

I've also tried using OpenSSL directly on the opened socket, like here https://stackoverflow.com/a/41321247/1676217, by initializing OpenSSL and setting its file descriptor to sctx->sock which is the same socket passed to CURL (i.e. calling SSL_set_fd(ssl, sctx->sock);) but SSL_Connect() fails.

I can't change the code entirely to remove CURL or to remove the external socket creation as both are used in other parts of the code, though I can attempt to adapt it.

Basically, I'd like to keep the function declarations for thread_recv_line() and thread_send_line() and make them use SSL.

If anyone proficient with OpenSSL, CURL and/or sockets could show me how to do it within this code snippet, I'd be very grateful.

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#define socket_blocks() (errno == EAGAIN || errno == EWOULDBLOCK)

#define RBUFSIZE 2048
#define RECVSIZE (RBUFSIZE - 4)

struct ctx {
    char *url;
    CURL *curl;
    char *curl_url;
    char curl_err_str[CURL_ERROR_SIZE];
    curl_socket_t sock;
    size_t sockbuf_size;
    char *sockbuf;
    pthread_mutex_t sock_lock;
};

static curl_socket_t opensocket_grab_cb(void *clientp, curlsocktype purpose,
    struct curl_sockaddr *addr)
{
    curl_socket_t *sock = (curl_socket_t*) clientp;
    *sock = socket(addr->family, addr->socktype, addr->protocol);
    return *sock;
}

bool connect(struct ctx *sctx, const char *url)
{
    CURL *curl;
    int rc;

    pthread_mutex_lock(&sctx->sock_lock);
    if (sctx->curl)
        curl_easy_cleanup(sctx->curl);
    sctx->curl = curl_easy_init();
    if (!sctx->curl) {
        pthread_mutex_unlock(&sctx->sock_lock);
        return false;
    }
    curl = sctx->curl;
    if (!sctx->sockbuf) {
        sctx->sockbuf = (char*) calloc(RBUFSIZE, 1);
        sctx->sockbuf_size = RBUFSIZE;
    }
    sctx->sockbuf[0] = '\0';
    pthread_mutex_unlock(&sctx->sock_lock);

    if (url != sctx->url) {
        free(sctx->url);
        sctx->url = strdup(url);
    }
    free(sctx->curl_url);
    sctx->curl_url = (char*) malloc(strlen(url));
    sprintf(sctx->curl_url, "http%s", strstr(url, "://"));

    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
    curl_easy_setopt(curl, CURLOPT_URL, sctx->curl_url);
    curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1);
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30);
    curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, sctx->curl_err_str);
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
    curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1);
    curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1);
    curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_keepalive_cb);
    curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_grab_cb);
    curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &sctx->sock);
    curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1);

    rc = curl_easy_perform(curl);
    if (rc) {
        curl_easy_cleanup(curl);
        sctx->curl = NULL;
        return false;
    }

    return true;
}

static bool send_line(curl_socket_t sock, char *s)
{
    size_t sent = 0;
    int len = (int) strlen(s);
    s[len++] = '\n';
    while (len > 0) {
        struct timeval timeout = {0, 0};
        int n;
        fd_set wd;
        FD_ZERO(&wd);
        FD_SET(sock, &wd);
        if (select((int) (sock + 1), NULL, &wd, NULL, &timeout) < 1)
            return false;
        n = send(sock, s + sent, len, 0);
        if (n < 0) {
            if (!socket_blocks())
                return false;
            n = 0;
        }
        sent += n;
        len -= n;
    }
    return true;
}

bool thread_send_line(struct ctx *sctx, char *s)
{
    bool ret = false;
    pthread_mutex_lock(&sctx->sock_lock);
    ret = send_line(sctx->sock, s);
    pthread_mutex_unlock(&sctx->sock_lock);
    return ret;
}

static bool socket_full(curl_socket_t sock, int timeout)
{
    struct timeval tv;
    fd_set rd;
    FD_ZERO(&rd);
    FD_SET(sock, &rd);
    tv.tv_sec = timeout;
    tv.tv_usec = 0;
    if (select((int)(sock + 1), &rd, NULL, NULL, &tv) > 0)
        return true;
    return false;
}

static void buffer_append(struct ctx *sctx, const char *s)
{
    size_t old, n;
    old = strlen(sctx->sockbuf);
    n = old + strlen(s) + 1;
    if (n >= sctx->sockbuf_size) {
        sctx->sockbuf_size = n + (RBUFSIZE - (n % RBUFSIZE));
        sctx->sockbuf = (char*) realloc(sctx->sockbuf, sctx->sockbuf_size);
    }
    strcpy(sctx->sockbuf + old, s);
}

char *thread_recv_line(struct ctx *sctx)
{
    ssize_t len, buflen;
    char *tok, *sret = NULL;

    if (!strstr(sctx->sockbuf, "\n")) {
        bool ret = true;
        time_t rstart;
        time(&rstart);

        if (!socket_full(sctx->sock, 60))
            return sret;
        do {
            char s[RBUFSIZE];
            ssize_t n;

            memset(s, 0, RBUFSIZE);
            n = recv(sctx->sock, s, RECVSIZE, 0);
            if (!n) {
                ret = false;
                break;
            }
            if (n < 0) {
                if (!socket_blocks() || !socket_full(sctx->sock, 1)) {
                    ret = false;
                    break;
                }
            } else
                buffer_append(sctx, s);
        } while (time(NULL) - rstart < 60 && !strstr(sctx->sockbuf, "\n"));

        if (!ret)
            return sret;
    }

    buflen = (ssize_t) strlen(sctx->sockbuf);
    tok = strtok(sctx->sockbuf, "\n");
    if (!tok)
        return sret;
    sret = strdup(tok);
    len = (ssize_t) strlen(sret);

    if (buflen > len + 1)
        memmove(sctx->sockbuf, sctx->sockbuf + len + 1, buflen - len + 1);
    else
        sctx->sockbuf[0] = '\0';
}
Normadize
  • 1,156
  • 12
  • 22
  • 3
    There is no such thing as a *"SSL socket"*. A [socket](https://en.wikipedia.org/wiki/Network_socket) is just one endpoint of a TCP/IP connection. SSL is a protocol used to encrypt the data that is sent through the TCP/IP connection. You need to use a library that implements SSL. – axiac Mar 13 '18 at 06:20
  • @n.m. Indeed I did try to use the SSL options to curl and they have no effect. My understanding is that it's because the socket is created externally and passed to curl via CURLOPT_OPENSOCKETFUNCTION. I'd be grateful for any concrete suggestions. – Normadize Mar 13 '18 at 18:40
  • @n.m. If you read the question I'm stating this is 3rd party code. I'm asking for help and already said I tried CURL's SSL options and failed. Telling me i should solve the problem is kind of moot ... if you have a concrete solution, then by all means, I'm all ears. – Normadize Mar 13 '18 at 22:01
  • I'm hardly a curl expert, but in my dealings with it, curl emitted sufficient diagnostics messages to the console that I was able to work through my configuration issues, including various initial SSL-related ones. Have you looked at what curl itself says about why it fails? – 500 - Internal Server Error Mar 14 '18 at 00:08
  • @500-InternalServerError it doesn't actually fail, it just doesn't encrypt data. I edited the question to underline that `CURLOPT_CONNECT_ONLY` is set to 1, i.e. curl is told to not handle data transfers. If I enable `CURLOPT_USE_SSL` the initial connection is in fact successful (my `connect()` returns true), but there is no SSL encryption for data transfers, i.e. the `send_line()` still sends data unencrypted. I still have a feeling that the fact the socket was created externally has an effect. However, the comments above are too dismissive/unhelpful for me to learn anything ... – Normadize Mar 14 '18 at 01:36
  • @n.m. I'll wait for others to provide some input as your replies are simply unhelpful. You don't need to reply any more, thank you. To others: as I explained, I need to keep the declarations for `thread_send_line()` and `thread_recv_line()` and enable SSL. I edited the question to further clarify what I tried and how it fails. I'd be grateful for actual suggestions on what to do (i.e. some code), rather than be told to fix it myself or scrap everything. – Normadize Mar 14 '18 at 06:55
  • I apologise for suggesting that this is not possible. I have used curl many years ago when relevant APIs weren't available, or I just could not remember them. Apparently one can do it relatively easily now, see the answer. – n. m. could be an AI Mar 14 '18 at 11:54

2 Answers2

0

This is not what you asked for, but you should consider placing a separate TCP-to-TLS proxy (such as HAProxy) between your code and remote server.

You will have to neither mess with TLS protocol details, nor modify existing code. Such setup will likely be safer and faster than anything you could write yourself.

user1643723
  • 4,109
  • 1
  • 24
  • 48
  • Thanks, but I have already been using stunnel for this (which works) but I need to make it work in the client's code to avoid launching external apps. I'll edit the question to specify that I don't want a proxy. – Normadize Mar 14 '18 at 08:07
0

Libcurl establishes an SSL connection with the server, even with CURLOPT_CONNECT_ONLY. It is not possible to establish another SSL connection on top of the same TCP connection. The only way to continue is to use the same SSL connection that libcurl has already established.

Fortunately it is possible to retrieve the SSL connection data from the curl handle using curl_easy_getinfo and CURLINFO_TLS_SSL_PTR. It should return a pointer of type SSL*.

The thread_send_line and thread_recv_line functions should not use the socket directly, but call SSL_read and SSL_write on the retrieved SSL pointer instead.

See CURLINFO_TLS_SSL_PTR for more information. Make sure you are getting an SSL pointer and not an SSL_CTX pointer.

Something like this should work:

bool thread_send_line(struct ctx *sctx, char *s)
{    
    bool ret = false;
    struct curl_tlssessioninfo* session;
    curl_easy_getinfo (sctx->curl, CURLINFO_TLS_SSL_PTR, &session);
    SSL* ssl = session->internals;

    pthread_mutex_lock(&sctx->sock_lock);

    int len = (int) strlen(s);
    s[len++] = '\n';
    ret = SSL_write(ssl, s, len);

    pthread_mutex_unlock(&sctx->sock_lock);
    return ret == len;    
}
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Thanks, I'll try this and see how far I get with it. Sounds like it should work. I was already patching in OpenSSL using an `SSL*` pointer, by trying to set the fd for SSL to the socket opened by `socket()` (with CURL opening a non-SSL connection) but it failed -- as I mentioned in the edited question. Your suggestion gives me some hope. For reference (during my next struggle with it) I'd be grateful if you'd be so kind to post the modded code lines that you think should work. – Normadize Mar 16 '18 at 06:48
  • This answer is wrong. `CURLINFO_TLS_SSL_PTR` is incompatible with `CURLOPT_CONNECT_ONLY`, and it will return `nullptr` in internal pointer field always. – Vitalii Nov 05 '18 at 11:16