beginner – Basic Cpp TCP TLS Client-Server hashing code based on OpenSSL1.1.1 (detailed instruction included)

This is a part of the graduate research assistant screening process.

The following are the aim of my implementation.

  1. Create a client and server program communicating over TCP socket.
  2. Establish TLS connection between the client and server
  3. Implement a hash service on the server
  • Take a number from the client
  • Send this number over TLS to the server
  • The server uses the Sha256 hash function within the OpenSSL library to generate a hash code for the number
  • The server sends the hash code back to the client for display.

I implemented ‘Client.cpp’ and ‘Client.cpp’ that do the above aim.

Any opinion including the following is welcomed:

  • Application of best practices and design pattern usage
  • Potential security issues
  • Performance
  • Correctness in unanticipated cases
  • Scalability
  • Formatting
  • Does my implementation exactly do what the aim describes?

I made self-signed
Code was developed and tested using

  • Ubuntu 20.04
  • OpenSSL 1.1.1
  • g++ 7.5.0

(please ignore some instruction even if it does not match with your environment. I wrote it somewhere else and copied it. There is no problem when you review this code. The incongruence is intentional, not a mistake.)

1. install openssl-1.1.1i

1.1. download openssl-1.1.1i here : https://www.openssl.org/source/

1.2 You may follow this instruction on how to install openssl : https://askubuntu.com/questions/1102803/4.how-to-upgrade-openssl-1-1-0-to-1-1-1-in-ubuntu-18-04

NOTE : after download, change ‘openssl-1.1.1g’ into ‘openssl-1.1.1i’

2. Secure a certificate and a private key

Do 2.1 or 2.2

2.1. create your own certificate

openssl req -new -x509 -nodes -config ssl.conf -out cert.pem -keyout key.pem -days 365

ssl.conf is uploaded.

For more information on how to create certificates, see: https://linuxize.com/post/creating-a-self-signed-ssl-certificate/

2.2 use uploaded certificate (cert.pem) and a private key (key.pem)

For convenient testing, I made ssl.conf my myself. Using ssl.conf, I made a certificate and a private key. That is, the Certificate Authority is a single self-signed certificate only I have access to. I know a private key should not be shared. This is only for convenient testing.

3. run code

(Server)

g++ -Wall -o Server Server.cpp -lssl -lm -lcrypto 
I uploaded 'Server'. Therefore, you may skip it. This is only for convenient testing. 

./Server (port) (cert.pem's path) (private key.pem's path) (the number of clients are allowed to request connection) 
./Server 9876 ./cert.pem ./ket.pem 5 

(Client)

g++ -Wall -o Client Client.cpp -lssl -lm -lcrypto 
I uploaded 'Server'. Therefore, you may skip it. This is only for convenient testing. 

./Server (hostname) (port) 
./Client 127.0.0.1 9876 
You can see results in localhost if you enter 127.0.0.1.

You must start Server first. 
The Server's port and the Client's port must be identical. 

reference

  1. socket programming in cpp: https://www.geeksforgeeks.org/socket-programming-cc/
  2. how to compile and debug cpp file in visual studio code : https://code.visualstudio.com/docs/cpp/config-linux
  3. TLS Server : https://wiki.openssl.org/index.php/Simple_TLS_Server
  4. TLS Client : https://gist.github.com/vedantroy/d2b99d774484cf4ea5165b200888e414
  5. openssl and compile : https://stackoverflow.com/questions/8945963/compile-c-and-openssl-on-ubuntu-11-10
  6. code with code review : Send and receive data code using OpenSSL
  7. sha256 : https://stackoverflow.com/questions/2262386/generate-sha256-with-openssl-and-c

Server.cpp

#include <iostream> 
#include <unistd.h> 
#include <string.h>
#include <iomanip>
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <openssl/ssl.h>
#include <openssl/err.h>
using namespace std;

SSL_CTX *configure_context() { 
    if(OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL) != 1){
        perror("OPENSSL_init_ssl() failed");
        exit(EXIT_FAILURE);
    }

    const SSL_METHOD *method = SSLv23_server_method();
    if(method == NULL) {
        perror("SSLv23_server_method() failed");
        exit(EXIT_FAILURE);
    }

    // create SSL context
    SSL_CTX *ctx = SSL_CTX_new(method);
    if (!ctx) {
        perror("SSL_CTX_new() failed");
        exit(EXIT_FAILURE);
    }

    // SSL_CTX_set1_curves() sets the supported curves for ctx to clistlen curves in the array clist.
    if(SSL_CTX_set_ecdh_auto(ctx, 1) != 1) {
        perror("SSL_CTX_set_ecdh_auto() failed");
        exit(EXIT_FAILURE);
    }

    return ctx;
}

void configure_keys(SSL_CTX *ctx, char *certificate, char *private_key) {
    if (SSL_CTX_use_certificate_file(ctx, certificate, SSL_FILETYPE_PEM) <= 0) {
        perror("SSL_CTX_use_certificate_file() failed");
        exit(EXIT_FAILURE);
    }

    if (SSL_CTX_use_PrivateKey_file(ctx, private_key, SSL_FILETYPE_PEM) <= 0) {
        perror("SSL_CTX_use_PrivateKey_file() failed");
        exit(EXIT_FAILURE);
    }
}

string sha256(const string input) {
    unsigned char hash(SHA256_DIGEST_LENGTH);
    SHA256_CTX sha256;

    // initialize a SHA256_CTX structure.
    if(SHA256_Init(&sha256) != 1) {
        perror("SHA256_Init() failed");
        exit(EXIT_FAILURE);
    }

    // SHA256_Update() is called repeatedly with chunks of the message to be hashed (input.c_str() bytes at input.size()).
    if(SHA256_Update(&sha256, input.c_str(), input.size()) != 1) {
        perror("SHA256_Update() failed");
        exit(EXIT_FAILURE);
    }

    // SHA256_Final() places the message digest in 'hash', 
    // which must have space for SHA256_DIGEST_LENGTH (32) bytes of output, and erases the SHA256_CTX.
    if(SHA256_Final(hash, &sha256) != 1) {
        perror("SHA256_Final() failed");
        exit(EXIT_FAILURE);
    }

    stringstream hash_code;
    for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
        hash_code << hex << setw(2) << setfill('0') << (int)hash(i);
    }
    return hash_code.str();
}

int main(int argc, char *argv()) { 
    SSL_CTX *ctx = configure_context();

    configure_keys(ctx, argv(2), argv(3));

    // create a client and server program communicating over TCP socket
    // create a TCP socket
    int server_socket = socket(PF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        perror("socket() failed");
        exit(EXIT_FAILURE);
    }
    cout << "(current state) socket()" << endl;

    struct sockaddr_in server_address;
    memset(&server_address, 0, sizeof(server_address));
    server_address.sin_family = AF_INET;
    // convert 4 byte-integer into network bytes
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    // convert 2 byte-integer into network bytes
    server_address.sin_port = htons(atoi(argv(1)));

    if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) < 0) {
        perror("bind() failed");
        exit(EXIT_FAILURE);
    }
    cout << "(current state) bind()" << endl;

    if (listen(server_socket, atoi(argv(4))) < 0) {
        perror("listen() failed");
        exit(EXIT_FAILURE);
    }
    cout << "(current state) listen()" << endl;

    // server waits for Client to connect
    while (1) {
        char input(1024) = { 0 };
        char output(1024) = { 0 }; 

        struct sockaddr_in client_address;
        int size_client_address = sizeof(client_address);
        int client_socket = accept(server_socket, (struct sockaddr *)&client_address, (socklen_t *)&size_client_address);
        if (client_socket < 0) {
            perror("accept() failedn");
            exit(EXIT_FAILURE);
        }
        cout << "(current state) accept()" << endl;

        // establish TLS connection between the client and server
        SSL *ssl = SSL_new(ctx);
        if (ssl == nullptr) {
            perror("SSL_new() failedn");
            exit(EXIT_FAILURE);
        }        

        if(SSL_set_fd(ssl, client_socket) != 1) {
            perror("SSL_set_fd() failedn");
            exit(EXIT_FAILURE);
        }

        if (SSL_accept(ssl) <= 0) {
            perror("SSL_accept() failedn");
            continue;
        }

        // get a number from Client
        int read_length = SSL_read(ssl, (char *)input, 1024);
        if (read_length <= 0) {
            perror("SSL_read() failedn");
            continue;
        }

        if (strcmp(input, "(exit)") == 0) {
            cout << "(terminate server)" << endl;
            SSL_free(ssl);
            close(client_socket);
            break;
        }

        // implement a hash service on the server 
        // the server uses the Sha256 hash function within the OpenSSL library to generate a hash code for the number 
        string hash_code = sha256(input);

        // the server sends the hash code back to the client for display. 
        int length = sprintf(output, "(server's hash code): %s", hash_code.c_str());
        int written_length = SSL_write(ssl, output, length);
        if (written_length <= 0) {
            perror("SSL_write() failedn");
            continue;
        } 
        cout << output << endl;
        
        // shut down a TLS/SSL connection
        if(SSL_shutdown(ssl) != 1) {
            perror("SSL_shutdown() failedn");
        }
        // free an allocated SSL structure
        SSL_free(ssl);
        close(client_socket);
    } 

    close(server_socket);
    SSL_CTX_free(ctx);
    // load and free error strings
    ERR_free_strings(); 

    return 0;
}

Client.cpp

#include <iostream>
#include <unistd.h> 
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h> 
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

using namespace std;

const int ERROR_STATUS = -1;

SSL_CTX *configure_context() {
    const SSL_METHOD *method = TLS_client_method();
    if (method == NULL) {
        perror("TLS_client_method() failedn");
        exit(EXIT_FAILURE);
    }

    SSL_CTX *ctx = SSL_CTX_new(method);
    if (ctx == NULL) {
        perror("SSL_CTX_new() failedn");
        exit(EXIT_FAILURE);
    }
    return ctx;
}

void display_certs(SSL *ssl) {
    // get server's certificate
    X509 *cert = SSL_get_peer_certificate(ssl); 
    if (cert == nullptr) {
        perror("SSL_get_peer_certificate() failed. No client certificates configuredn");
        exit(EXIT_FAILURE);
    }

    cout << "(server certificates)" << endl;
    char *line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
    if(line == NULL) {
        perror("X509_NAME_oneline(X509_get_subject_name(), 0, 0) failedn");
        exit(EXIT_FAILURE);
    }
    cout << "subject: " << line << endl;
    delete line;

    line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
    if(line == NULL) {
        perror("X509_NAME_oneline(X509_get_issuer_name(), 0, 0) failedn");
        exit(EXIT_FAILURE);
    }
    cout << "issuer: " << line << endl;
    delete line;

    X509_free(cert);
}

int main(int argc, char *argv()) {
    cout << "enter (exit) to end this program" << endl;

    SSL_CTX *ctx = configure_context(); 

    // create an SSL structure for a connection
    SSL *ssl = SSL_new(ctx);
    if (ssl == NULL) {
        perror("SSL_new() failedn");
        exit(EXIT_FAILURE);
    } 

    struct hostent *host = gethostbyname(argv(1));
    if (host == nullptr) {
        perror("gethostbyname() failedn");
        exit(EXIT_FAILURE);
    }

    struct addrinfo hints = {0}, *addrs;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    if (getaddrinfo(argv(1), argv(2), &hints, &addrs) != 0) {
        perror("getaddrinfo() failedn");
        exit(EXIT_FAILURE);
    }

    int client_socket;
    for (struct addrinfo *addr = addrs; addr != nullptr; addr = addr->ai_next) {
        client_socket = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
        if (client_socket == ERROR_STATUS) {
            perror("perror() failedn");
            continue;
        }
        cout << "(current state) socket()n";

        if (connect(client_socket, addr->ai_addr, addr->ai_addrlen) == 0) {
            cout << "(current state) connect()" << endl;
            break; 
        }

        client_socket = ERROR_STATUS;
        close(client_socket);
    }

    freeaddrinfo(addrs);

    if (client_socket == ERROR_STATUS) {
        perror("connect() failedn");
        exit(EXIT_FAILURE);
    }

    // connect the SSL object (ssl) with a file descriptor (client_socket)
    if (SSL_set_fd(ssl, client_socket) != 1) {
        perror("SSL_set_fd() failedn");
        exit(EXIT_FAILURE);
    }

    // initiate the TLS/SSL handshake with an TLS/SSL server
    if (SSL_connect(ssl) != 1) {
        perror("SSL_connect() failedn");
        exit(EXIT_FAILURE);
    }
    cout << "(current state) connected with " << SSL_get_cipher(ssl) << " encryption" << endl;

    // print certificate information which is set by Server
    // I added this to make sure certificate is applied
    display_certs(ssl);

    while (1) {
        // take a number from client
        cout << "(enter a number): ";
        string sent;
        getline(cin, sent);
        if (sent == "") {
            continue;
        }

        // send this number over TLS to the server
        int written_length = SSL_write(ssl, sent.c_str(), sent.length());
        if (written_length <= 0) {
            SSL_get_error(ssl, written_length);
            continue;
        }

        if (strcmp(sent.c_str(), "(exit)") == 0) {
            cout << "(terminate client)" << endl;
            break;
        }

        // a stack can only hold 1024 kilobytes of data
        // to avoid the risk of a stack-overflow, I set array size 1024 
        char input(1024) = { 0 };
        int read_length = SSL_read(ssl, (char *)input, 1024);
        if (read_length <= 0) {
            SSL_get_error(ssl, read_length);
            continue;
        }
        cout << input << endl;
        break;
    }

    close(client_socket);
    SSL_CTX_free(ctx);

    return 0;
}