lundi 2 novembre 2020

OpenSSL DTLS server implementation (C++)

I am trying to implement a DTLS server wrapper in C++.
What I intend to do is when I receive a message, if the client is unknown then assume it's a handshake and try to complete it. If the client is known, assume it's a regular session message.

The issue I have here is that my program expects the same message to be sent twice to process it correctly-ish (once for the 'is the peer known?' part and once for the SSL_accept/SSL_read part).

I assume I'm not setting my BIOs correctly but I have no idea what the correct way of doing this is.

Here is what my files look like at the moment:

/* server.hpp */

#ifndef _DTLS_SERVER_
#define _DTLS_SERVER_


#include <atomic>
#include <mutex>
#include <vector>

#include <arpa/inet.h>
#include <assert.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <map>
#include <algorithm>

class DTLSServer{

    private:




    public:

    static int generate_cookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len); //placeholder

    static int verify_cookie(SSL *ssl, const unsigned char *cookie, unsigned int cookie_len); //placeholder
    struct client
    {
        BIO *bio;
        SSL *ssl;
        std::string onConnect(const uint16_t& p_sock, const std::string&, SSL_CTX *p_ctx);
        std::string onMessage(const std::string&);
    };

    // fd to use
    void init(const int &sock);


    DTLSServer();
    ~DTLSServer();

    //void stop(int p_signal);

    // if unknown pair => adds new client => handshake else returns decyphered message
    std::string autoConnect(const sockaddr_in &p_addr, const std::string p_string);

    // remove client p_addr:p_port
    void removeClient(const std::string &p_addr, const uint16_t &p_port);

    // return added client p_addr:p_port
    client &createClient(const std::string &p_addr, const uint16_t &p_port);

    private:
    
    SSL_CTX *i_ctx;
    std::map<std::pair<std::string, uint16_t>, client>i_clients;
    uint64_t i_securitySessionsLifeTime;
    std::mutex i_dtlsMutex;
    int i_sock;

    public:

};

#endif
/* server.cpp */

#include "server.hpp"
#include <unistd.h>
#include <iostream>
#include <thread>

char cookie_str[] = "cookie";

DTLSServer::DTLSServer(){}

DTLSServer::~DTLSServer()
{
    i_dtlsMutex.lock();
    for(auto &l_cli : i_clients)
    {
        removeClient(l_cli.first.first, l_cli.first.second);
    }
    SSL_CTX_free(i_ctx);
    i_dtlsMutex.unlock();
}

std::string DTLSServer::autoConnect(const sockaddr_in &p_addr, const std::string p_string){

    char l_strAddress[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &p_addr.sin_addr.s_addr, l_strAddress, INET_ADDRSTRLEN);

    std::string l_addr(l_strAddress);
    uint16_t l_port = ntohs(p_addr.sin_port);

    std::lock_guard<std::mutex>l_guard(i_dtlsMutex);

    auto l_cli = std::find_if(i_clients.begin(),i_clients.end(), // looking for {addr, port} in clients
        [l_addr, l_port](const std::pair<std::pair<std::string, uint16_t>, client> &el) // {(addr,port),client}
        {
            return l_addr == el.first.first
            && l_port == el.first.second;
        });
    if(l_cli!=i_clients.end()) // found a client
    {
        std::cout << "received message from " << l_addr << ":" << l_port << std::endl;
        std::string retString = l_cli->second.onMessage(p_string); // decyphers message
        if(retString == "TO REMOVE")
            removeClient(l_addr, l_port);
        return retString;
    }
    else // new client
    {
        std::cout << "adding " << l_addr << ":" << l_port << std::endl;
        client &l_client = createClient(l_addr, l_port); // adding new client
        auto l_newcli = std::find_if(i_clients.begin(),i_clients.end(),
        [l_addr, l_port](const std::pair<std::pair<std::string, uint16_t>, client> &el)
        {
            return l_addr == el.first.first
            && l_port == el.first.second;
        });
        if(l_newcli==i_clients.end())
        {
            std::cout << "couldn't find added client" << std::endl;
            return "";
        }
        else
            std::cout << "valid" << std::endl;
        return l_newcli->second.onConnect(i_sock, p_string, i_ctx); // creating SSL session for new client
    }
}

DTLSServer::client &DTLSServer::createClient(const std::string &p_addr, const uint16_t &p_port)
{
    std::pair<std::string, uint16_t> l_pair = {p_addr,p_port};
    i_clients.insert(std::pair<std::pair<std::string, uint16_t>, client>(l_pair,client()));
    return i_clients.at(l_pair);
}

void DTLSServer::removeClient(const std::string &p_addr, const uint16_t &p_port)
{
    auto l_cli = std::find_if(i_clients.begin(),i_clients.end(), // looking for {addr, port} in clients
        [&p_addr, &p_port](const std::pair<std::pair<std::string, uint16_t>, client> &el) // {(addr,port),client}
        {
            return p_addr == el.first.first
            && p_port == el.first.second;
        });
    if(l_cli!=i_clients.end()){
        SSL_free(l_cli->second.ssl);
        l_cli->second.ssl = nullptr;
        i_clients.erase(l_cli);
    }
    else
    {
        std::cout << "could not delete" << std::endl;
    }
}

void DTLSServer::init(const int &sock)
{
    i_sock = sock;
    SSL_load_error_strings();
    SSL_library_init();
    OpenSSL_add_all_ciphers();
    memcpy(cookie_str, "cookie", 7);

    i_ctx = SSL_CTX_new(DTLS_server_method());
    /*if(SSL_CTX_set_cipher_list(i_ctx, "AES256-SHA256")!=1) //CLIENT PART
    {
        std::cout << "Could not set cipher" << std::endl;
    }*/
    SSL_CTX_set_min_proto_version(i_ctx, DTLS1_2_VERSION);
    SSL_CTX_use_certificate_chain_file(i_ctx, "server-cert.pem");
    SSL_CTX_use_PrivateKey_file(i_ctx, "server-key.pem", SSL_FILETYPE_PEM);
    int l_ret = SSL_CTX_load_verify_locations(i_ctx, "root-ca.pem", NULL);
    fprintf(stderr, "SSL_CTX_load_verify_locations -> %d\n", l_ret);
    l_ret = SSL_CTX_set_default_verify_file(i_ctx);
    fprintf(stderr, "SSL_CTX_set_default_verify_file -> %d\n", l_ret);
    SSL_CTX_set_verify(i_ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);

    SSL_CTX_set_cookie_generate_cb(i_ctx, generate_cookie);
    SSL_CTX_set_cookie_verify_cb(i_ctx, verify_cookie);
}

std::string DTLSServer::client::onConnect(const uint16_t & p_sock, const std::string& p_msg, SSL_CTX *p_ctx)
{
    std::cout << "about to connect" << std::endl;
    ssl = SSL_new(p_ctx);
    if(nullptr==ssl)
        std::cout << "couldn't create ssl session" << std::endl;
    bio = BIO_new_dgram(p_sock, BIO_NOCLOSE);
    if(nullptr==bio)
        std::cout << "couldn't create ssl bio" << std::endl;
    SSL_set_bio(ssl, bio, bio);
    //write(p_sock, p_msg.c_str(), p_msg.size());
    //BIO_write(bio, p_msg.c_str(), p_msg.size());
    
    int needed = BIO_get_read_request(bio);
    std::cout << "about to accept"<< std::endl;
    int ret = SSL_accept(ssl);
    //int ret = 0;
    std::cout << "accepted"<< std::endl;
    if (ret < 0)
    {
        std::cout << "failed to complete Handshake as " << strerror(errno) << std::endl;    
    }
    return "";
}

std::string DTLSServer::client::onMessage(const std::string& p_msg)
{

    int err = BIO_write(bio, &p_msg[0], p_msg.size());
    
    int needed = BIO_get_read_request(bio);
    
    char l_msg[1500] = {0};
    /*if(needed > 0) 
        std::cout << "critical error" << std::endl;*/
    int ret = SSL_read(ssl, &l_msg[0], 1500);
    if(ret < 0)
    {
        std::cout << "unvalid message" << std::endl;
        return "TO REMOVE";
    }
    else if(0 == ret)
    {
        std::cout << "client to remove" << std::endl;
        return "TO REMOVE";
    }
    else
    {
        std::cout << "valid message" << std::endl;
        std::string l_m{l_msg};
        return "MESSAGE IS : " + l_m;
    }
    return "";

}


int DTLSServer::generate_cookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len)
{
    memmove(cookie, cookie_str, sizeof(cookie_str)-1);
    *cookie_len = sizeof(cookie_str)-1;

    return 1;
}

int DTLSServer::verify_cookie(SSL *ssl, const unsigned char *cookie, unsigned int cookie_len)
{
    return sizeof(cookie_str)-1==cookie_len && memcmp(cookie, cookie_str, sizeof(cookie_str)-1)==0;
}
/* main.cpp */

#include "server.hpp"
#include <iostream>

int main()
{
    DTLSServer l_dtls;
    int l_socket;
    if ((l_socket = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        std::cout << "Could not create socket. " << strerror(errno) << std::endl;
        return 1;
    }

    struct sockaddr_in l_sendingAddress;
    l_sendingAddress.sin_family = AF_INET;
    inet_pton(AF_INET,
              "127.0.0.19",
              &l_sendingAddress.sin_addr.s_addr);
    l_sendingAddress.sin_port = htons(8888);

    //Binding socket
    if(bind(l_socket,reinterpret_cast<sockaddr *>(&l_sendingAddress),sizeof(l_sendingAddress)) < 0)
    {
        std::cout << "Could not bind. " << strerror(errno) << std::endl;
        return 1;
    }


    l_dtls.init(l_socket);
    char l_buffer[2000] = {0};
    sockaddr_in l_client;
    socklen_t l_clientLen = sizeof(l_client);
    while(1)
    {
        ssize_t l_bytesReceived = recvfrom(l_socket, &l_buffer, 2000, MSG_DONTWAIT, reinterpret_cast<sockaddr *>(&l_client), &l_clientLen);
        if (l_bytesReceived == -1)
        {
            continue;
        }
        std::string l_string{l_buffer, static_cast<size_t>(l_bytesReceived)};
        std::cout << l_dtls.autoConnect(l_client, l_string) << std::endl;
        memset(l_buffer, 0, 2000);
    }
    return 0;
}

My work is mainly based off the following source: https://github.com/stepheny/openssl-dtls-custom-bio

How can I set my BIOs to do what I want them to do?
Do I have to create custom BIOs?

Thanks in advance for your suggestions :)

Aucun commentaire:

Enregistrer un commentaire