This is a part of the graduate research assistant screening process.
The following are the aim of my implementation.
- Create a client and server program communicating over TCP socket.
- Establish TLS connection between the client and server
- 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
- socket programming in cpp: https://www.geeksforgeeks.org/socket-programming-cc/
- how to compile and debug cpp file in visual studio code : https://code.visualstudio.com/docs/cpp/config-linux
- TLS Server : https://wiki.openssl.org/index.php/Simple_TLS_Server
- TLS Client : https://gist.github.com/vedantroy/d2b99d774484cf4ea5165b200888e414
- openssl and compile : https://stackoverflow.com/questions/8945963/compile-c-and-openssl-on-ubuntu-11-10
- code with code review : Send and receive data code using OpenSSL
- 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;
}