/*
  JS 2024-04-11 testovane s OpenSSL 3.3.0, liboqs 0.10.0 a oqs-provider 0.6.0
*/
/*
  JS 2024-04-11 testovane s OpenSSL 3.3.0, liboqs 0.10.0 a oqs-provider 0.5.3
*/
/*
    JS 2024-03-04 - doplnenie funkcii pre uvolnenie nacitanych providerov
*/
/*
   JS 2024-02-24 - funkcia initSSLContext() rozsirena o nacitanie OQS-providera
		   pre plne funkcne PQ algoritmy na kazdej platforme
		 - doplnene priklady nastavenia premennej DEFAULT_GROUPS
		 - testovane s oqsprovider 0.5.3
*/
/*
   JS 2024-02-17 - testovanie PQ algoritmov s pouzitim oqs-providera
		 - pridane vypisy s informaciami o pouzivanych algoritmoch
		   pre KEX a certifikaty
*/

/*
   JS 2024-02-08 testovane s aktualnou najnovsou verziou OpenSSL 3.2.1
*/

/*
    Name: Martin Janitor
    Subject: DP1
    Description: Implemenation SSL/TLS [CLIENT] connection with usage of BIO structure
    Date: 2023-07-12
*/

#include <stdio.h>
#include <errno.h>
#include <malloc.h>
#include <string.h>

#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/provider.h>

/*
Premenna na definovanie "exchange groups", algoritmov na vymenu klucov
Server moze podporovat viacero KEX/KEM algoritmov, v premennej musi byt kazdy
algoritmus oddeleny dvojbodkou, napr. "kyber512:bikel1"
Podporovane algoritmy je mozne ziskat prikazom "openssl list -kem-algorithms"
Prazdna premenna alebo naplnena neplatnymi protokolmi sposobi prerusenie aplikacie

Ak server a klient nenajdu spolocny KEX/KEM protokol, tak
SSL vyhodi chybu SSL routines:final_key_share:no suitable key share

Priklad pouzitia:
- klasicke algoritmy: "x25519:x448:prime256v1:secp521r1:secp384r1:ffdhe2048:ffdhe3072"
- PQ algoritmy: "kyber512:kyber768:kyber1024:bikel1:bikel3:bikel5:hqc128:hqc192:hqc256:frodo640aes:frodo640shake:frodo976aes:frodo976shake:frodo1344aes:frodo1344shake"
- hybrid algoritmy: "x25519_kyber768:x25519_frodo640aes:x25519_hqc128:x448_bikel3:x448_kyber768:p256_kyber768"

Podporovane algoritmy OQS-providerom: https://github.com/open-quantum-safe/oqs-provider/blob/0.6.0/ALGORITHMS.md 

POZOR - BIKE protokol nefunguje na Windows platforme (liboqs 0.10.0, oqs-provider 0.6.0)

*/
#define DEFAULT_GROUPS "X25519:kyber512:kyber768"

/**
 * printUsage function who describe the utilisation of this script.
 * @param char* bin : the name of the current binary.
 */
void printHeader(char *bin)
{
    /* JS update */
    printf("[?] Usage : %s <hostname> <port> <client_private_key> <client_public_key>\n", bin);
    return;
}

/**
 * makeClientSocket function who create a traditionnal client socket to the hostname throught the port.
 * @param char* hostname : the target to connect to
 * @param int port : the port to connect throught
 * @return BIO context structure
 */
BIO* makeClientSocket(const char *hostname, int port)
{
    char buffer[25];
    memset(buffer,'\0',25);
    sprintf(buffer, "%s:%d",hostname,port);

    BIO *bio = BIO_new_connect(buffer);

    if (bio == NULL){
        fprintf(stderr, "Error creating connection BIO\n");
        return NULL;
    }
    return bio;
}

/** JS update
 * initSSLContext function who initialize the SSL/TLS engine with right method/protocol
 * SSL/TLS engine provided by PQ algorithms functions of OQS provider
 * @param client_key name of file where is stored private key of client
 * @param client_pem name of file where is stored public key of client
 * @return SSL_CTX *ctx ; a pointer to the SSL context created
*/
SSL_CTX *initSSLContext(char *client_key, char *client_pem)
{
    const SSL_METHOD *method;
    SSL_CTX *ctx;

    // initialize the SSL library
    SSL_library_init();
    SSL_load_error_strings();
    OpenSSL_add_all_algorithms();

    // MJ only TLS connection
    method = TLS_client_method();

    // create new context from selected method
    ctx = SSL_CTX_new(method); 
    if (ctx == NULL){
        ERR_print_errors_fp(stderr);
        abort();
    }

    // MD zabezpecenie overenia certifikatu servera pomocou CA
    if (SSL_CTX_load_verify_locations(ctx, "myCA.pem", 0)){
        printf("CA certificate loaded\n");
        SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
    }

    else{
        printf("\nCA certificate not loaded! Abort ...\n");
        abort();
    }

// MD nacitanie dat pre autentizaciu klineta
// MJ Update [Define Macro]
#ifdef AUTHENTICATION
    int res = 0;

    res = SSL_CTX_use_certificate_file(ctx, client_pem, SSL_FILETYPE_PEM);
    if (res <= 0){
        // handle error
	ERR_print_errors_fp(stderr);
        printf("\nCLIENT certificate not loaded! Abort ...\n");
        abort();
    }

    res = SSL_CTX_use_PrivateKey_file(ctx, client_key, SSL_FILETYPE_PEM);
    if (res <= 0){
        // handle error
        printf("\nCLIENT key not loaded! Abort ...\n");
        abort();
    }

    /* verify private key */
    if (!SSL_CTX_check_private_key(ctx)){
        fprintf(stderr, "Private key does not match the public certificate\n");
        abort();
    }
    
    printf("Certificate attached\n");
    printf("\n");
#endif

    return ctx;
}

/**
 * showCerts function who catch and print out certificat's data from the server
 * @param SSL* ssl : the SSL/TLS connection
 */
void showCerts(SSL *ssl)
{
    X509 *cert;
    char *subject, *issuer;

    // get the server's certificate
    cert = SSL_get_peer_certificate(ssl); 
    if (cert != NULL){

	// JS get server's certificate algorithm name
	int nid;
	SSL_get_peer_signature_type_nid(ssl, &nid);
	printf("Server signature algorithm: %s\n", OBJ_nid2sn(nid));

	// get certificat's subject
        subject = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); 
        // get certificat's issuer
        issuer = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);   

        printf("[+] Server certificates :\n");
        printf("\tSubject: %s\n", subject);
        printf("\tIssuer: %s\n", issuer);

        // Free memory
        free(subject);
        free(issuer);
        X509_free(cert);

        // check certificat's trust
        if (SSL_get_verify_result(ssl) == X509_V_OK) 
            printf("[+] Server certificates X509 is trust!\n");
        else
            printf("[-] Server certificates X509 is not trust...\n");
    }
    else
        printf("[-] No server's certificates\n");
    return;
}

/**
 * main function who coordinate the socket and SSL connection creation, then receive and 
   emit data to and from the server.
 */
int main(int argc, char **argv)
{
    BIO* sock = NULL;
    int bytes, port;
    SSL_CTX *ctx;
    SSL *ssl;
    char buf[1024];
    char *hostname;

    if (argc != 5){
        printHeader(argv[0]);
        exit(0);
    }

    // MJ Add keys for communication
    char *client_key = argv[3];
    char *client_pem = argv[4];

    hostname = argv[1];

    // Assign correct port number
    port = (atoi(argv[2]) > 0 && atoi(argv[2]) < 65535) ? atoi(argv[2]) : 433;

    // Load default and OQS providers
    // Default provider must be loaded before OQS provider
    OSSL_PROVIDER* provider;
    provider = OSSL_PROVIDER_load(NULL, "default");
    if (provider == NULL) {
    	printf("Failed to load Default provider\n");
	    exit(0);
    }

    OSSL_PROVIDER* custom_provider = OSSL_PROVIDER_load(NULL, "oqsprovider");
    if (custom_provider == NULL){
        printf("Failed to load OQS-provider\n");
        OSSL_PROVIDER_unload(provider);
        exit(0);
    }

    // load SSL library and dependances
    ctx = initSSLContext(client_key, client_pem); 

    // make a classic socket to the hostname throught the port
    sock = makeClientSocket(hostname, port);   
    
    // create new SSL connection state   
    ssl = SSL_new(ctx);

    // JS set default groups for key exchange/encapsulation
    // Without this function, server will use default X25519 protocol
    // First protocol supported by both server and client will be used for KEX/KEM
    // Empty DEFAULT_GROUPS or list of unsupported protocol by client will fail communication      
    if (SSL_set1_groups_list(ssl, DEFAULT_GROUPS) != 1){
	printf("KEX/KEM algorithms undefined unknown - check DEFAULT_GROUPS variable\n");
	ERR_print_errors_fp(stderr);
      	SSL_free(ssl);
       	SSL_CTX_free(ctx);
        OSSL_PROVIDER_unload(provider);
        OSSL_PROVIDER_unload(custom_provider);  
    	exit(0);
    }                           

    // attach the socket descriptor
    SSL_set_bio(ssl, sock, sock);

    if (SSL_connect(ssl) == -1) // make the SSL connection
        ERR_print_errors_fp(stderr);
    else
    {
	// JS get chosen (negotiated) key exchange/encapsulation algorithm name
	printf("Used group (KEM): %s\n", SSL_group_to_name(ssl, SSL_get_negotiated_group(ssl)));

        /*
            if the server suddenly wants a new handshake,
            OpenSSL handles it in the background. Without this
            option, any read or write operation will return an
            error if the server wants a new handshake.
        */
        SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); 

        char msg[] = "ClientName";
        printf("[+] Cipher used : %s\n", SSL_get_cipher(ssl));

        // show certificats data
        showCerts(ssl);  

        // encrypt and send message
        SSL_write(ssl, msg, strlen(msg));

        // get response and decrypt content
        bytes = SSL_read(ssl, buf, sizeof(buf)); 
        buf[bytes] = 0;
        printf("[+] Server data received : %s\n", buf);
        SSL_shutdown(ssl);
    }
    
    // release SSL connection state
    SSL_free(ssl);
    
    // release SSL's context
    SSL_CTX_free(ctx); 
    // JS Unload both providers
    OSSL_PROVIDER_unload(provider);
    OSSL_PROVIDER_unload(custom_provider);   
    return 0;
}
