/*
 * ==================================================================================
 * Cryptographic Engine - AES-256-CBC encryption/decryption
 *
 * ISA Project - ICMP Covert Channel File Transfer System
 * Author: Roman Necas (xnecasr00)
 * Date: 13. October 2025
 *
 * DESCRIPTION:
 * This module implements the cryptographic operations for secure file transfer.
 * It provides AES-256-CBC encryption/decryption with PBKDF2 key derivation,
 * ensuring that files are protected during transmission over the network.
 *
 * KEY FUNCTIONS:
 * - crypto_init() - Initialize crypto engine with password, salt, and IV
 * - crypto_init_encrypt() - Set up encryption context (client side)
 * - crypto_init_decrypt() - Set up decryption context (server side)
 * - crypto_encrypt() - Encrypt data chunk (streaming)
 * - crypto_decrypt() - Decrypt data chunk (streaming)
 * - crypto_finalize_encrypt() - Complete encryption and get final block with padding
 * - crypto_finalize_decrypt() - Complete decryption and remove padding
 * - crypto_cleanup() - Securely wipe keys and free resources
 *
 * ENCRYPTION ALGORITHM:
 * - Cipher: AES-256-CBC (Advanced Encryption Standard, 256-bit key, Cipher Block Chaining)
 * - Key Size: 256 bits (32 bytes)
 * - Block Size: 128 bits (16 bytes)
 * - IV Size: 128 bits (16 bytes)
 * - Padding: PKCS#7 (automatic via OpenSSL EVP interface)
 *
 * KEY DERIVATION:
 * - Algorithm: PBKDF2 (Password-Based Key Derivation Function 2)
 * - Hash Function: SHA-256
 * - Iterations: 600,000 (high iteration count for strong security)
 * - Salt Size: 32 bytes (PBKDF2_SALT_SIZE)
 * - Password Source: User's login name (global 'login' variable)
 *
 * PER-SESSION SECURITY:
 * - Each file transfer uses unique IV (Initialization Vector)
 * - Each file transfer uses unique salt for key derivation
 * - IV and salt are generated with OpenSSL's RAND_bytes() (cryptographically secure)
 * - IV and salt are transmitted in START packet metadata
 * - Same password + different salt = different encryption key
 *
 * STREAMING ENCRYPTION:
 * The module supports streaming encryption/decryption, allowing large files
 * to be processed in chunks without loading the entire file into memory:
 *
 * Client workflow:
 *   1. crypto_init(&crypto, login, NULL, NULL);  // Generates random IV and salt
 *   2. crypto_init_encrypt(&crypto);             // Initialize encryption context
 *   3. Loop: crypto_encrypt(&crypto, chunk, ...)  // Encrypt each chunk
 *   4. crypto_finalize_encrypt(&crypto, ...)     // Get final padded block
 *   5. crypto_cleanup(&crypto);                  // Securely wipe key material
 *
 * Server workflow:
 *   1. crypto_init(&crypto, login, received_salt, received_iv);  // Use client's IV/salt
 *   2. crypto_init_decrypt(&crypto);             // Initialize decryption context
 *   3. Loop: crypto_decrypt(&crypto, chunk, ...)  // Decrypt each chunk
 *   4. crypto_finalize_decrypt(&crypto, ...)     // Remove padding from final block
 *   5. crypto_cleanup(&crypto);                  // Securely wipe key material
 *
 * SECURITY FEATURES:
 * - Secure key wiping: Uses OPENSSL_cleanse() to prevent key recovery from memory
 * - Strong key derivation: 600,000 PBKDF2 iterations (recommended by OWASP 2023)
 * - Unique per-transfer parameters: New IV and salt for every file transfer
 * - Cryptographically secure random: Uses OpenSSL's RAND_bytes() for IV/salt generation
 * - Error handling: Returns -1 on any cryptographic operation failure
 *
 * IMPLEMENTATION NOTES:
 * - Uses OpenSSL's EVP (Envelope) API for high-level crypto operations
 * - EVP API automatically handles padding and block alignment
 * - crypto_engine_t structure maintains persistent state across chunks
 * - Must call crypto_init_encrypt/decrypt before first encrypt/decrypt call
 * - Must call crypto_finalize_encrypt/decrypt after last encrypt/decrypt call
 * - Failure to finalize may result in incomplete decryption (missing final bytes)
 *
 * THREAD SAFETY:
 * This module is NOT thread-safe. Each thread must use its own crypto_engine_t
 * instance. OpenSSL's EVP_CIPHER_CTX is not thread-safe.
 * ==================================================================================
 */

#include "secret.h"

/*
 * derive_key_from_login - Derive 256-bit AES key from password using PBKDF2
 *
 * Uses PBKDF2-HMAC-SHA256 with 600,000 iterations to convert a user password
 * into a cryptographically strong encryption key. High iteration count provides
 * protection against brute-force attacks.
 *
 * @param login: User's password (login name in this application)
 * @param salt: Random salt for key derivation (32 bytes)
 * @param key: Output buffer for derived key (32 bytes for AES-256)
 * @return: 0 on success, -1 on failure
 */
static int derive_key_from_login(const char *login, const uint8_t *salt, uint8_t *key) {
    ERROR_CHECK(login != NULL, ERR_INVALID_ARGS, "Login cannot be NULL");
    ERROR_CHECK(salt != NULL, ERR_INVALID_ARGS, "Salt cannot be NULL");
    ERROR_CHECK(key != NULL, ERR_INVALID_ARGS, "Key buffer cannot be NULL");

    /* PKCS5_PBKDF2_HMAC: Password-Based Key Derivation Function 2
     * - Uses HMAC-SHA256 as the pseudorandom function
     * - 600,000 iterations (OWASP 2023 recommendation for PBKDF2-SHA256)
     * - Derives 32-byte key for AES-256 */
    if (PKCS5_PBKDF2_HMAC(login, strlen(login), salt, PBKDF2_SALT_SIZE,
                          PBKDF2_ITERATIONS, EVP_sha256(), AES_KEY_SIZE, key) != 1) {
        ERROR_SET(ERR_CRYPTO_KEY_DERIVE, "PBKDF2 key derivation failed");
        ERR_print_errors_fp(stderr);  /* Print OpenSSL error details */
        return -1;
    }

    return 0;
}

/*
 * crypto_init - Initialize crypto engine with key derivation
 *
 * Sets up the crypto engine by:
 * 1. Creating an OpenSSL EVP cipher context
 * 2. Generating or using provided salt and IV
 * 3. Deriving AES-256 key from password using PBKDF2
 *
 * CLIENT usage: Call with salt=NULL, iv=NULL to generate new random values
 * SERVER usage: Call with salt and iv from START packet to match client's key
 *
 * @param crypto: Crypto engine structure to initialize
 * @param login: User's password for key derivation
 * @param salt: 32-byte salt (NULL to generate new random salt)
 * @param iv: 16-byte IV (NULL to generate new random IV)
 * @return: 0 on success, -1 on failure
 */
int crypto_init(crypto_engine_t *crypto, const char *login, const uint8_t *salt, const uint8_t *iv) {
    if (!crypto || !login) {
        print_error("Invalid parameters for crypto initialization");
        return -1;
    }

    /* Clear the crypto structure to ensure clean state */
    memset(crypto, 0, sizeof(crypto_engine_t));

    /* Create OpenSSL EVP cipher context (EVP = Envelope, high-level crypto API) */
    crypto->ctx = EVP_CIPHER_CTX_new();
    if (!crypto->ctx) {
        print_error("Failed to create cipher context");
        ERR_print_errors_fp(stderr);
        return -1;
    }

    /* Handle salt: use provided (server) or generate new (client) */
    if (salt) {
        /* Server mode: use salt received from client in START packet */
        memcpy(crypto->salt, salt, PBKDF2_SALT_SIZE);
    } else {
        /* Client mode: generate cryptographically secure random salt */
        if (RAND_bytes(crypto->salt, PBKDF2_SALT_SIZE) != 1) {
            print_error("Failed to generate random salt");
            ERR_print_errors_fp(stderr);
            EVP_CIPHER_CTX_free(crypto->ctx);
            return -1;
        }
    }

    /* Handle IV (Initialization Vector): use provided (server) or generate new (client) */
    if (iv) {
        /* Server mode: use IV received from client in START packet */
        memcpy(crypto->iv, iv, AES_IV_SIZE);
    } else {
        /* Client mode: generate cryptographically secure random IV */
        if (RAND_bytes(crypto->iv, AES_IV_SIZE) != 1) {
            print_error("Failed to generate random IV");
            ERR_print_errors_fp(stderr);
            EVP_CIPHER_CTX_free(crypto->ctx);
            return -1;
        }
    }

    /* Derive AES-256 key from password using PBKDF2 with the salt */
    if (derive_key_from_login(login, crypto->salt, crypto->key) != 0) {
        print_error("Failed to derive encryption key");
        EVP_CIPHER_CTX_free(crypto->ctx);
        return -1;
    }

    crypto->initialized = 1;  /* Mark as initialized */
    return 0;
}

/*
 * crypto_init_encrypt - Initialize encryption mode
 *
 * Configures the EVP context for AES-256-CBC encryption.
 * Must be called after crypto_init() and before any crypto_encrypt() calls.
 *
 * @param crypto: Initialized crypto engine
 * @return: 0 on success, -1 on failure
 */
int crypto_init_encrypt(crypto_engine_t *crypto) {
    if (!crypto || !crypto->initialized) {
        print_error("Crypto engine not initialized");
        return -1;
    }

    /* Initialize EVP context for encryption with AES-256-CBC
     * - EVP_aes_256_cbc(): AES cipher with 256-bit key in CBC mode
     * - crypto->key: 32-byte key derived from password
     * - crypto->iv: 16-byte initialization vector
     * CBC mode: each block XORed with previous ciphertext block */
    if (EVP_EncryptInit_ex(crypto->ctx, EVP_aes_256_cbc(), NULL, crypto->key, crypto->iv) != 1) {
        print_error("Failed to initialize encryption");
        ERR_print_errors_fp(stderr);
        return -1;
    }

    return 0;
}

/*
 * crypto_init_decrypt - Initialize decryption mode
 *
 * Configures the EVP context for AES-256-CBC decryption.
 * Must be called after crypto_init() and before any crypto_decrypt() calls.
 *
 * @param crypto: Initialized crypto engine
 * @return: 0 on success, -1 on failure
 */
int crypto_init_decrypt(crypto_engine_t *crypto) {
    if (!crypto || !crypto->initialized) {
        print_error("Crypto engine not initialized");
        return -1;
    }

    /* Initialize EVP context for decryption with AES-256-CBC
     * Must use same key and IV as encryption to successfully decrypt */
    if (EVP_DecryptInit_ex(crypto->ctx, EVP_aes_256_cbc(), NULL, crypto->key, crypto->iv) != 1) {
        print_error("Failed to initialize decryption");
        ERR_print_errors_fp(stderr);
        return -1;
    }

    return 0;
}

/*
 * crypto_encrypt - Encrypt data chunk (streaming encryption)
 *
 * Encrypts data using AES-256-CBC. Can be called multiple times to encrypt
 * a large file in chunks. IMPORTANT: Must call crypto_finalize_encrypt()
 * after all chunks to get the final padded block.
 *
 * NOTE: EVP_EncryptUpdate may buffer data internally and return 0 bytes.
 * This is normal behavior for block ciphers - data is held in the context
 * until a full block is available.
 *
 * @param crypto: Initialized crypto engine
 * @param plaintext: Input data to encrypt
 * @param plaintext_len: Length of input data
 * @param ciphertext: Output buffer for encrypted data (must be plaintext_len + AES_BLOCK_SIZE)
 * @param ciphertext_len: Output - actual bytes of ciphertext produced (may be 0 if buffered)
 * @return: 0 on success, -1 on failure
 */
int crypto_encrypt(crypto_engine_t *crypto, const uint8_t *plaintext, int plaintext_len,
                   uint8_t *ciphertext, int *ciphertext_len) {
    if (!crypto || !plaintext || !ciphertext || !ciphertext_len) {
        print_error("Invalid parameters for encryption");
        return -1;
    }

    if (!crypto->initialized) {
        print_error("Crypto engine not initialized");
        return -1;
    }

    /* EVP_EncryptUpdate: Encrypt data chunk
     * - May buffer data internally (returns 0 bytes if not enough for full block)
     * - CBC mode processes 16-byte blocks
     * - Buffered data will be output in subsequent calls or finalize */
    int len;
    if (EVP_EncryptUpdate(crypto->ctx, ciphertext, &len, plaintext, plaintext_len) != 1) {
        print_error("Encryption update failed");
        ERR_print_errors_fp(stderr);
        return -1;
    }

    *ciphertext_len = len;  /* Actual bytes produced (may be 0) */
    return 0;
}

/*
 * crypto_decrypt - Decrypt data chunk (streaming decryption)
 *
 * Decrypts data using AES-256-CBC. Can be called multiple times to decrypt
 * a large file in chunks. IMPORTANT: Must call crypto_finalize_decrypt()
 * after all chunks to get the final unpadded block.
 *
 * @param crypto: Initialized crypto engine
 * @param ciphertext: Input encrypted data
 * @param ciphertext_len: Length of encrypted data
 * @param plaintext: Output buffer for decrypted data (must be at least ciphertext_len)
 * @param plaintext_len: Output - actual bytes of plaintext produced
 * @return: 0 on success, -1 on failure
 */
int crypto_decrypt(crypto_engine_t *crypto, const uint8_t *ciphertext, int ciphertext_len,
                   uint8_t *plaintext, int *plaintext_len) {
    if (!crypto || !ciphertext || !plaintext || !plaintext_len) {
        print_error("Invalid parameters for decryption");
        return -1;
    }

    if (!crypto->initialized) {
        print_error("Crypto engine not initialized");
        return -1;
    }

    /* EVP_DecryptUpdate: Decrypt data chunk
     * - Processes complete 16-byte blocks
     * - May buffer final partial block until finalize */
    int len;
    if (EVP_DecryptUpdate(crypto->ctx, plaintext, &len, ciphertext, ciphertext_len) != 1) {
        print_error("Decryption update failed");
        ERR_print_errors_fp(stderr);
        return -1;
    }

    *plaintext_len = len;  /* Actual bytes decrypted */
    return 0;
}

/*
 * crypto_finalize_encrypt - Finalize encryption and get final padded block
 *
 * MUST be called after all crypto_encrypt() calls to:
 * 1. Flush any buffered data from the cipher context
 * 2. Apply PKCS#7 padding to the final block
 *
 * PKCS#7 padding: If data is not block-aligned, padding bytes are added.
 * Each padding byte contains the number of padding bytes added (1-16).
 * Even if data is block-aligned, a full padding block (16 bytes of 0x10) is added.
 *
 * Example: If last block has 13 bytes, add 3 bytes of 0x03
 *          If data is exactly block-aligned, add 16 bytes of 0x10
 *
 * @param crypto: Initialized crypto engine
 * @param output: Buffer for final encrypted block (must be at least AES_BLOCK_SIZE = 16 bytes)
 * @param output_len: Output - bytes in final block (0-16 bytes)
 * @return: 0 on success, -1 on failure
 */
int crypto_finalize_encrypt(crypto_engine_t *crypto, uint8_t *output, int *output_len) {
    if (!crypto || !output || !output_len) {
        print_error("Invalid parameters for encryption finalization");
        return -1;
    }

    if (!crypto->initialized) {
        print_error("Crypto engine not initialized");
        return -1;
    }

    /* EVP_EncryptFinal_ex: Get final encrypted block with PKCS#7 padding
     * - Flushes any buffered plaintext
     * - Adds padding to reach block boundary
     * - Returns final ciphertext block */
    if (EVP_EncryptFinal_ex(crypto->ctx, output, output_len) != 1) {
        print_error("Encryption finalization failed");
        ERR_print_errors_fp(stderr);
        return -1;
    }

    return 0;
}

/*
 * crypto_finalize_decrypt - Finalize decryption and remove padding
 *
 * MUST be called after all crypto_decrypt() calls to:
 * 1. Flush any buffered data from the cipher context
 * 2. Verify and remove PKCS#7 padding from the final block
 *
 * FAILURE HERE INDICATES:
 * - Wrong decryption key (passwords don't match)
 * - Corrupted ciphertext
 * - Invalid padding (data tampered with)
 *
 * @param crypto: Initialized crypto engine
 * @param output: Buffer for final decrypted block
 * @param output_len: Output - bytes in final block after removing padding
 * @return: 0 on success, -1 on failure
 */
int crypto_finalize_decrypt(crypto_engine_t *crypto, uint8_t *output, int *output_len) {
    if (!crypto || !output || !output_len) {
        print_error("Invalid parameters for decryption finalization");
        return -1;
    }

    if (!crypto->initialized) {
        print_error("Crypto engine not initialized");
        return -1;
    }

    /* EVP_DecryptFinal_ex: Get final decrypted block and verify padding
     * - Flushes any buffered ciphertext
     * - Verifies PKCS#7 padding is correct
     * - Removes padding bytes
     * - Returns only the actual plaintext (no padding)
     * FAILS if: wrong key, corrupted data, invalid padding */
    if (EVP_DecryptFinal_ex(crypto->ctx, output, output_len) != 1) {
        print_error("Decryption finalization failed - possible wrong key or corrupted data");
        ERR_print_errors_fp(stderr);
        return -1;
    }

    return 0;
}

/*
 * crypto_cleanup - Securely cleanup crypto engine and wipe sensitive data
 *
 * CRITICAL SECURITY FUNCTION: Uses OPENSSL_cleanse() to securely wipe
 * cryptographic keys from memory. Regular memset() can be optimized away
 * by the compiler, but OPENSSL_cleanse() is guaranteed to overwrite memory.
 *
 * This prevents key recovery from:
 * - Memory dumps
 * - Swap files
 * - Core dumps
 * - Cold boot attacks (to some extent)
 *
 * @param crypto: Crypto engine to cleanup
 */
void crypto_cleanup(crypto_engine_t *crypto) {
    if (crypto) {
        /* Free OpenSSL cipher context */
        if (crypto->ctx) {
            EVP_CIPHER_CTX_free(crypto->ctx);
            crypto->ctx = NULL;
        }

        /* CRITICAL: Securely wipe all cryptographic material from memory
         * OPENSSL_cleanse() is guaranteed not to be optimized away by compiler */
        OPENSSL_cleanse(crypto->key, AES_KEY_SIZE);    /* Wipe 256-bit AES key */
        OPENSSL_cleanse(crypto->iv, AES_IV_SIZE);      /* Wipe 128-bit IV */
        OPENSSL_cleanse(crypto->salt, PBKDF2_SALT_SIZE); /* Wipe 256-bit salt */
        crypto->initialized = 0;
    }
}

/*
 * crypto_get_salt - Retrieve salt for transmission to server
 *
 * Client calls this to get the salt to include in START packet.
 * Server will use this salt to derive the same encryption key.
 *
 * @param crypto: Initialized crypto engine
 * @param salt: Output buffer (must be PBKDF2_SALT_SIZE = 32 bytes)
 */
void crypto_get_salt(const crypto_engine_t *crypto, uint8_t *salt) {
    if (crypto && salt && crypto->initialized) {
        memcpy(salt, crypto->salt, PBKDF2_SALT_SIZE);
    }
}

/*
 * crypto_get_iv - Retrieve IV for transmission to server
 *
 * Client calls this to get the IV to include in START packet.
 * Server will use this IV to decrypt data.
 *
 * @param crypto: Initialized crypto engine
 * @param iv: Output buffer (must be AES_IV_SIZE = 16 bytes)
 */
void crypto_get_iv(const crypto_engine_t *crypto, uint8_t *iv) {
    if (crypto && iv && crypto->initialized) {
        memcpy(iv, crypto->iv, AES_IV_SIZE);
    }
}

/*
 * crypto_secure_zero - Securely wipe memory
 *
 * Uses OPENSSL_cleanse() to ensure memory is actually zeroed
 * and not optimized away by compiler.
 *
 * @param ptr: Pointer to memory to wipe
 * @param size: Number of bytes to wipe
 */
void crypto_secure_zero(void *ptr, size_t size) {
    if (ptr && size > 0) {
        OPENSSL_cleanse(ptr, size);
    }
}

/*
 * crypto_secure_compare - Constant-time memory comparison
 *
 * Uses CRYPTO_memcmp() which runs in constant time regardless of
 * where differences occur. This prevents timing attacks.
 *
 * Regular memcmp() returns as soon as first difference is found,
 * which can leak information about the compared values through
 * timing analysis.
 *
 * @param a: First buffer
 * @param b: Second buffer
 * @param size: Number of bytes to compare
 * @return: 0 if equal, non-zero if different, -1 on error
 */
int crypto_secure_compare(const void *a, const void *b, size_t size) {
    if (!a || !b) {
        return -1;
    }
    /* CRYPTO_memcmp: constant-time comparison (prevents timing attacks) */
    return CRYPTO_memcmp(a, b, size);
}

/*
 * crypto_derive_key - Generic key derivation with custom parameters
 *
 * More flexible version of derive_key_from_login() that allows
 * custom salt length, key length, and iteration count.
 *
 * @param password: Password string
 * @param salt: Salt bytes
 * @param salt_len: Length of salt
 * @param key: Output buffer for derived key
 * @param key_len: Desired key length
 * @param iterations: PBKDF2 iteration count
 * @return: 0 on success, -1 on failure
 */
int crypto_derive_key(const char *password, const uint8_t *salt, size_t salt_len,
                      uint8_t *key, size_t key_len, int iterations) {
    ERROR_CHECK(password != NULL, ERR_INVALID_ARGS, "Password cannot be NULL");
    ERROR_CHECK(salt != NULL, ERR_INVALID_ARGS, "Salt cannot be NULL");
    ERROR_CHECK(key != NULL, ERR_INVALID_ARGS, "Key buffer cannot be NULL");
    ERROR_CHECK(salt_len > 0, ERR_INVALID_ARGS, "Salt length must be positive");
    ERROR_CHECK(key_len > 0, ERR_INVALID_ARGS, "Key length must be positive");
    ERROR_CHECK(iterations > 0, ERR_INVALID_ARGS, "Iterations must be positive");

    /* PBKDF2 with configurable parameters */
    if (PKCS5_PBKDF2_HMAC(password, strlen(password), salt, salt_len,
                          iterations, EVP_sha256(), key_len, key) != 1) {
        ERROR_SET(ERR_CRYPTO_KEY_DERIVE, "PBKDF2 key derivation failed");
        ERR_print_errors_fp(stderr);
        return -1;
    }

    return 0;
}

/*
 * crypto_random_bytes - Generate cryptographically secure random bytes
 *
 * Uses OpenSSL's RAND_bytes() which provides cryptographically strong
 * pseudo-random numbers suitable for cryptographic purposes.
 *
 * @param buffer: Output buffer for random bytes
 * @param size: Number of random bytes to generate
 * @return: 0 on success, -1 on failure
 */
int crypto_random_bytes(uint8_t *buffer, size_t size) {
    ERROR_CHECK(buffer != NULL, ERR_INVALID_ARGS, "Buffer cannot be NULL");
    ERROR_CHECK(size > 0, ERR_INVALID_ARGS, "Size must be positive");

    /* RAND_bytes: Generate cryptographically strong random bytes
     * - Uses OpenSSL's CSPRNG (Cryptographically Secure Pseudo-Random Number Generator)
     * - Suitable for keys, IVs, salts, session IDs, nonces
     * - NOT suitable if PRNG is not properly seeded (rare on modern systems) */
    if (RAND_bytes(buffer, size) != 1) {
        ERROR_SET(ERR_CRYPTO_INIT, "Failed to generate random bytes");
        ERR_print_errors_fp(stderr);
        return -1;
    }

    return 0;
}