/*
  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
		 - uprava vypisu o pripojeni klienta
		 - pridane vypisy s informaciami o pouzivanych algoritmoch
		   pre KEX a certifikaty
		 - oprava kontroly navratovej hodnoty funkcii 
			SSL_CTX_use_certificate_file() a SSL_CTX_use_PrivateKey_file
*/

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

/*
    Name: Martin Janitor
    Subject: DP1
    Description: Implemenation SSL/TLS [SERVER] connection with usage of BIO structure
    Date: 2023-06-28
*/

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

#include <openssl/crypto.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.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 "kyber768:frodo976aes:kyber1024"

#define DEFAULT_PORT 443


/**
 * 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 <port> <server_public_key> <server_private_key>]\n", bin);
    return;
}

/**
 * makeServerSocket function who create a BIO structure for socket
 * @param int port : the port to listen
 * @return int socket : the socket number created
 */
BIO* makeServerSocket(int port){

    char buffer[5];
    memset(buffer,'\0',5);
    sprintf(buffer,"%d",port);

    BIO* accept_bio = BIO_new_accept(buffer);
    if (accept_bio == NULL){
        fprintf(stderr, "Error creating accept BIO\n");
        return NULL;
    }
	
    printf("\n");
    printf("[+] Server listening on the %d port...\n", port);
    printf("[+] Waiting for connection\n");
    return accept_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
 * @return SSL_CTX *ctx : a pointer to the SSL context created
*/
SSL_CTX* initSSLContext(){
    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_server_method();

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

    return ctx;
}

/**
 * loadCertificates function who load private key and certificat from files.
 * 3 mecanisms available :
 * - loading certificate and private key from file(s)
 * - use embed hardcoded certificate and private key in the PEM format
 * @param SSL_CTX* ctx : the SSL/TLS context
 * @param char *server_pem : filename of the PEM certificat
 * @param char *server_key : filename of the PEM private key
 */
void loadCertificates(SSL_CTX* ctx, const char* server_pem, const char* server_key){
    

if (SSL_CTX_use_certificate_file(ctx, server_pem, SSL_FILETYPE_PEM) != 1 ||
        // namiesto MD SSL_CTX_use_RSAprivateKey_file
        SSL_CTX_use_PrivateKey_file(ctx, server_key, SSL_FILETYPE_PEM) != 1)
    { 

        ERR_print_errors_fp(stderr);
        abort();
    }
    else
        printf("[*] Server's certificat and private key loaded from file.\n");

    // verify private key match the public key into the certificate
    if(!SSL_CTX_check_private_key(ctx)){
        fprintf(stderr, "[-] Private key does not match the public certificate...\n");
        abort();
    } 
    else{
       	printf("[+] Server's private key match public certificate\n");
    }

    // JS get server's signature algorithm name
    X509 *cert = SSL_CTX_get0_certificate(ctx);
    int pknid;
    if (X509_get_signature_info(cert, NULL, &pknid, NULL, NULL) != 1){
    	printf("Certificate signature algorithm: Unknown algorithm");
    }
    else{
	printf("Certificate signature algorithm: %s\n", OBJ_nid2sn(pknid));
    }

    // MD  kod na autentizaciu klienta
    #ifdef AUTHENTICATION

    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);

    // load the trusted client CA certificate into context
    if (SSL_CTX_load_verify_locations(ctx, "myCA.pem", NULL) != 1)
    {
        fprintf(stderr, "[-] CA certificate not loaded...\n");
        abort();
    }
    #endif

    return;
}

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

    // get the client's certificate
    cert = SSL_get_peer_certificate(ssl);

    // get certificate info
    if (cert != NULL){
        // get certificate's subject
        subject = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); 
        // get certificate's issuer
        issuer = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);   

        printf("[+] Client certificates :\n");
        printf("\tSubject: %s\n", subject);
        printf("\tIssuer: %s\n", issuer);
        printf("[+] Cipher used : %s\n", SSL_get_cipher(ssl));

        // Free memory
        free(subject);
        free(issuer);
        X509_free(cert);
    }
    else
        printf("[-] No client's certificates\n");
    return;
}

/**
 * routine function who treat the content of data received and reply to the client.
 * this function is threadable and his context sharedable.
 * @param SSL* ssl : the SSL/TLS connection
 */
void routine(SSL* ssl)
{
    char buf[1024 / 2], reply[1024];

    int bytes;
    const char *echo = "Enchante %s, je suis ServerName.\n";

    // accept SSL/TLS connection
    if (SSL_accept(ssl) == -1) 
        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)));

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

    // show certificates data
    showCerts(ssl);                         
        
        // read data from client request
        bytes = SSL_read(ssl, buf, sizeof(buf)); 
        if (bytes > 0){
            buf[bytes] = 0;
            printf("[+] Client data received: %s\n", buf);

            // construct response
            sprintf(reply, echo, buf);    
            // send response        
            SSL_write(ssl, reply, strlen(reply)); 
        }
        else{
            switch (SSL_get_error(ssl, bytes)){
            	case SSL_ERROR_ZERO_RETURN:
                    printf("SSL_ERROR_ZERO_RETURN : ");
                    break;
            	case SSL_ERROR_NONE:
                    printf("SSL_ERROR_NONE : ");
                    break;
            	case SSL_ERROR_SSL:
                    printf("SSL_ERROR_SSL : ");
                    break;
            }
            ERR_print_errors_fp(stderr);
        }
    }
	
    SSL_shutdown(ssl);
    SSL_free(ssl);
}


/**
 * main function who coordinate the socket and SSL connection creation, then receive and 
   emit data to and from the client.
 */
int main(int argc, char **argv){
    BIO* sock;
    int port;
    SSL_CTX *ctx;
    const char  *server_pem, *server_key;

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

    // Select PORT number 
    port =  (atoi(argv[1]) > 0 && atoi(argv[1]) < 65535) ? atoi(argv[1]) : DEFAULT_PORT;

    // 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){
        OSSL_PROVIDER_unload(provider);
	    printf("Failed to load OQS-provider\n");
	    exit(0);
    }

    // load SSL library and dependances
    ctx =  initSSLContext();        
    server_pem = argv[2];
    server_key = argv[3];

     // load certificats and keys
    loadCertificates(ctx, server_pem, server_key);   

    // make a classic server socket
    sock  = makeServerSocket(port);           
	
    // Pridane na skusku od predosleho
    BIO_set_bind_mode(sock, BIO_BIND_REUSEADDR);

    if (BIO_do_accept(sock) <= 0)
        ERR_print_errors_fp(stderr);
 
    while (1)
    {
        // Accept incoming connection
        if (BIO_do_accept(sock) <= 0)
        {
            ERR_print_errors_fp(stderr);
            break;
        }
	
	    printf("\n");

        // Create a new BIO object
        BIO *conn = BIO_pop(sock);
        if (conn == NULL){
            ERR_print_errors_fp(stderr);
            break;
        }

        // JS added info about connected client (IP)
        printf("Connection of client %s accepted\n", BIO_get_peer_name(sock));

        // Create a new SSL object
        SSL *ssl = SSL_new(ctx);
        if (ssl == NULL){
            ERR_print_errors_fp(stderr);
            BIO_free(conn);
            break;
        }

        // JS set key exchange/encapsulation protocols supported by server
        // Without this function, client 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 server 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);
            BIO_free(conn);
            break;
	    }
	
        // Assign the SSL object to the connection BIO
        SSL_set_bio(ssl, conn, conn);

        routine(ssl);
        // JS remove break if you want server running in loop
        break;
    }

    SSL_CTX_free(ctx); 
    // JS Unload both providers
	OSSL_PROVIDER_unload(provider);
    OSSL_PROVIDER_unload(custom_provider);
    BIO_free_all(sock); 
    return 0;
}

