diff --git a/.rooignore b/.rooignore new file mode 100644 index 0000000..2ec0128 --- /dev/null +++ b/.rooignore @@ -0,0 +1 @@ +otp copy.c \ No newline at end of file diff --git a/Makefile b/Makefile index b43f0f9..b5b7a6f 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,24 @@ CC = gcc -CFLAGS = -Wall -Wextra -std=c99 +CFLAGS = -Wall -Wextra -std=c99 -Iinclude LIBS = -lm LIBS_STATIC = -static -lm TARGET = otp -SOURCE = otp.c -CHACHA20_SOURCE = nostr_chacha20.c +SOURCES = $(wildcard src/*.c) nostr_chacha20.c otp.c +OBJS = $(SOURCES:.c=.o) # Default build target -$(TARGET): $(SOURCE) - $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE) $(CHACHA20_SOURCE) $(LIBS) +$(TARGET): $(OBJS) + $(CC) $(CFLAGS) -o $(TARGET) $(OBJS) $(LIBS) # Static linking target -static: $(SOURCE) - $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE) $(CHACHA20_SOURCE) $(LIBS_STATIC) +static: $(OBJS) + $(CC) $(CFLAGS) -o $(TARGET) $(OBJS) $(LIBS_STATIC) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ clean: - rm -f $(TARGET) *.pad *.state + rm -f $(TARGET) $(OBJS) *.pad *.state install: sudo cp $(TARGET) /usr/local/bin/ diff --git a/include/otp.h b/include/otp.h new file mode 100644 index 0000000..47c0e5c --- /dev/null +++ b/include/otp.h @@ -0,0 +1,345 @@ +#ifndef OTP_H +#define OTP_H + +//////////////////////////////////////////////////////////////////////////////// +// OTP CIPHER - FUNCTION PROTOTYPES HEADER +// One Time Pad Implementation v0.2.109 +// +// This header file contains all function prototypes extracted from otp.c +// Organized by functional categories for better maintainability +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Constants +#define MAX_INPUT_SIZE 4096 +#define MAX_LINE_LENGTH 1024 +#define MAX_HASH_LENGTH 65 +#define PROGRESS_UPDATE_INTERVAL (64 * 1024 * 1024) // 64MB intervals +#define DEFAULT_PADS_DIR "pads" +#define FILES_DIR "files" +#define MAX_ENTROPY_BUFFER (4 * 1024 * 1024) // 4MB entropy buffer for large operations + +// Global variables - now managed through state module + +////////////////////////////////////////////////////////////////////////////// +// STATE MANAGEMENT FUNCTIONS +////////////////////////////////////////////////////////////////////////////// + +// State getters and setters +const char* get_current_pads_dir(void); +void set_current_pads_dir(const char* dir); +int get_interactive_mode(void); +void set_interactive_mode(int mode); +int get_terminal_width(void); +int get_terminal_height(void); +void set_terminal_dimensions(int width, int height); + +//////////////////////////////////////////////////////////////////////////////// +// TYPE DEFINITIONS +//////////////////////////////////////////////////////////////////////////////// + +// Decrypt operation modes for universal decrypt function +typedef enum { + DECRYPT_MODE_INTERACTIVE, // Interactive text decryption with prompts + DECRYPT_MODE_SILENT, // Silent text decryption (no prompts/labels) + DECRYPT_MODE_FILE_TO_TEXT, // File to text output with prompts + DECRYPT_MODE_FILE_TO_FILE // File to file output (binary) +} decrypt_mode_t; + +// Pad filter types for selection functions +typedef enum { + PAD_FILTER_ALL, // Show all pads + PAD_FILTER_UNUSED_ONLY // Show only unused pads (0% usage) +} pad_filter_type_t; + +// Enhanced entropy system state structure +typedef struct { + size_t target_bytes; // Target entropy to collect + size_t collected_bytes; // Bytes collected so far + size_t unique_keys; // Number of unique keys pressed + double collection_start_time; // Start timestamp + double last_keypress_time; // Last keypress timestamp + unsigned char quality_score; // Entropy quality (0-100) + int auto_complete_enabled; // Allow auto-complete at minimum + unsigned char key_histogram[256]; // Track key frequency +} entropy_collection_state_t; + +//////////////////////////////////////////////////////////////////////////////// +// CORE APPLICATION FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Main application entry points +int main(int argc, char* argv[]); +int interactive_mode(void); +int command_line_mode(int argc, char* argv[]); +int pipe_mode(int argc, char* argv[], const char* piped_text); + +//////////////////////////////////////////////////////////////////////////////// +// INPUT/OUTPUT DETECTION FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Stdin detection functions +int has_stdin_data(void); +char* read_stdin_text(void); + +//////////////////////////////////////////////////////////////////////////////// +// PREFERENCES MANAGEMENT FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Configuration and preferences handling +int load_preferences(void); +int save_preferences(void); +char* get_preference(const char* key); +int set_preference(const char* key, const char* value); +char* get_default_pad_path(void); +int set_default_pad_path(const char* pad_path); + +//////////////////////////////////////////////////////////////////////////////// +// HARDWARE DETECTION FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// OTP thumb drive detection function +int detect_otp_thumb_drive(char* otp_drive_path, size_t path_size); + +//////////////////////////////////////////////////////////////////////////////// +// USB DRIVE MANAGEMENT FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////////// +// EXTERNAL TOOL INTEGRATION FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Editor and file manager functions +char* get_preferred_editor(void); +char* get_preferred_file_manager(void); +int launch_text_editor(const char* initial_content, char* result_buffer, size_t buffer_size); +int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size); + +//////////////////////////////////////////////////////////////////////////////// +// CORE CRYPTOGRAPHIC OPERATIONS +//////////////////////////////////////////////////////////////////////////////// + +// Primary encryption/decryption functions +int generate_pad(uint64_t size_bytes, int show_progress); +int encrypt_text(const char* pad_identifier, const char* input_text); +int decrypt_text(const char* pad_identifier, const char* encrypted_message); +int encrypt_file(const char* pad_identifier, const char* input_file, const char* output_file, int ascii_armor); +int decrypt_file(const char* input_file, const char* output_file); +int decrypt_binary_file(FILE* input_fp, const char* output_file); +int decrypt_ascii_file(const char* input_file, const char* output_file); + +//////////////////////////////////////////////////////////////////////////////// +// ENHANCED ENTROPY SYSTEM FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Entropy source types +typedef enum { + ENTROPY_SOURCE_KEYBOARD = 1, + ENTROPY_SOURCE_DICE = 2, + ENTROPY_SOURCE_TRUERNG = 3, + ENTROPY_SOURCE_FILE = 4 +} entropy_source_t; + +// Terminal control for entropy collection +int setup_raw_terminal(struct termios* original_termios); +void restore_terminal(struct termios* original_termios); + +// Entropy collection and feedback +int collect_entropy_with_feedback(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int allow_early_exit); +void display_entropy_progress(const entropy_collection_state_t* state); +void draw_progress_bar(double percentage, int width); +void draw_quality_bar(double quality, int width, const char* label); + +// TrueRNG Device Constants (updated to match otp.c implementation) +#define TRUERNG_VID "04D8" +#define TRUERNG_PID "F5FE" +#define TRUERNGPRO_VID "16D0" +#define TRUERNGPRO_PID "0AA0" +#define TRUERNGPROV2_VID "04D8" +#define TRUERNGPROV2_PID "EBB5" + +// SwiftRNG Device Constants (same VID/PID as TrueRNG devices) +#define SWIFT_RNG_VID "04D8" +#define SWIFT_RNG_PID "F5FE" +#define SWIFT_RNG_PRO_VID "16D0" +#define SWIFT_RNG_PRO_PID "0AA0" +#define SWIFT_RNG_PRO_V2_VID "04D8" +#define SWIFT_RNG_PRO_V2_PID "EBB5" + +// TrueRNG/SwiftRNG Device Type enumeration +typedef enum { + TRUERNG_ORIGINAL = 1, + TRUERNG_PRO = 2, + TRUERNG_PRO_V2 = 3, + SWIFT_RNG = 4, + SWIFT_RNG_PRO = 5, + SWIFT_RNG_PRO_V2 = 6 +} truerng_device_type_t; + +// Hardware RNG device information structure +typedef struct { + char port_path[256]; // Device port path (e.g., /dev/ttyUSB0) + truerng_device_type_t device_type; // Device type identifier + char friendly_name[64]; // Human-readable device name + int is_working; // 1 if device passes basic test, 0 otherwise +} hardware_rng_device_t; + +// Hardware RNG device detection and selection functions +int detect_all_hardware_rng_devices(hardware_rng_device_t* devices, int max_devices, int* num_devices_found); +int test_hardware_rng_device(const hardware_rng_device_t* device); +int select_hardware_rng_device_interactive(hardware_rng_device_t* devices, int num_devices, hardware_rng_device_t* selected_device); +int find_truerng_port(char* port_path, size_t port_path_size, truerng_device_type_t* device_type); // Legacy function for backward compatibility + +// TrueRNG entropy collection functions (updated to match implementation) +int setup_truerng_serial_port(const char* port_path); +int collect_truerng_entropy(unsigned char* entropy_buffer, size_t target_bytes, size_t* collected_bytes, int display_progress); +int collect_truerng_entropy_from_device(const hardware_rng_device_t* device, unsigned char* entropy_buffer, + size_t target_bytes, size_t* collected_bytes, int display_progress); +int collect_truerng_entropy_streaming_from_device(const hardware_rng_device_t* device, const char* pad_chksum, + size_t total_bytes, int display_progress, int entropy_mode); +const char* get_truerng_device_name(truerng_device_type_t device_type); +int read_usb_device_info(const char* port_name, char* vid, char* pid); + +// Dice entropy collection functions (updated to match implementation) +int collect_dice_entropy(unsigned char* entropy_buffer, size_t target_bytes, size_t* collected_bytes, int display_progress); + +// Unified entropy collection interface (updated to match implementation) +int collect_entropy_by_source(entropy_source_t source, unsigned char* entropy_buffer, size_t target_bytes, size_t* collected_bytes, int display_progress); + +// Entropy quality calculation +double calculate_timing_quality(const entropy_collection_state_t* state); +double calculate_variety_quality(const entropy_collection_state_t* state); +unsigned char calculate_overall_quality(const entropy_collection_state_t* state); +double get_precise_time(void); + +// Entropy processing and application +int derive_chacha20_params(const unsigned char* entropy_data, size_t entropy_size, + unsigned char key[32], unsigned char nonce[12]); +int add_entropy_to_pad(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, int show_progress); +int add_entropy_direct_xor(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, uint64_t pad_size, int display_progress); +int add_entropy_chacha20(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, uint64_t pad_size, int display_progress); +int handle_add_entropy_to_pad(const char* pad_chksum); + +// Enhanced entropy system helper functions +int update_pad_checksum_after_entropy(const char* old_chksum, char* new_chksum); +int rename_pad_files_safely(const char* old_chksum, const char* new_chksum); +int is_pad_unused(const char* pad_chksum); + +//////////////////////////////////////////////////////////////////////////////// +// DIRECTORY MANAGEMENT FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Directory handling and path management +int ensure_pads_directory(void); +void get_pad_path(const char* chksum, char* pad_path, char* state_path); +const char* get_files_directory(void); +void get_default_file_path(const char* filename, char* result_path, size_t result_size); +void get_directory_display(const char* file_path, char* result, size_t result_size); + +//////////////////////////////////////////////////////////////////////////////// +// UTILITY FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// General utility and helper functions +uint64_t parse_size_string(const char* size_str); +char* find_pad_by_prefix(const char* prefix); +int show_pad_info(const char* chksum); +void show_progress(uint64_t current, uint64_t total, time_t start_time); +void format_time_remaining(double seconds, char* buffer, size_t buffer_size); + +//////////////////////////////////////////////////////////////////////////////// +// FILE OPERATIONS +//////////////////////////////////////////////////////////////////////////////// + +// File state and checksum operations +int read_state_offset(const char* pad_chksum, uint64_t* offset); +int write_state_offset(const char* pad_chksum, uint64_t offset); +int calculate_checksum(const char* filename, char* checksum_hex); +int calculate_checksum_with_progress(const char* filename, char* checksum_hex, int display_progress, uint64_t file_size); + +//////////////////////////////////////////////////////////////////////////////// +// UNIVERSAL CORE FUNCTIONS FOR CODE CONSOLIDATION +//////////////////////////////////////////////////////////////////////////////// + +// Consolidated cryptographic operations +int universal_xor_operation(const unsigned char* data, size_t data_len, + const unsigned char* pad_data, unsigned char* result); +int parse_ascii_message(const char* message, char* chksum, uint64_t* offset, char* base64_data); +int load_pad_data(const char* pad_chksum, uint64_t offset, size_t length, unsigned char** pad_data); +int generate_ascii_armor(const char* chksum, uint64_t offset, const unsigned char* encrypted_data, + size_t data_length, char** ascii_output); +int validate_pad_integrity(const char* pad_path, const char* expected_chksum); + +// Universal decrypt function - consolidates all decrypt operations +int universal_decrypt(const char* input_data, const char* output_target, decrypt_mode_t mode); + +//////////////////////////////////////////////////////////////////////////////// +// BASE64 ENCODING FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Custom base64 implementation +char* custom_base64_encode(const unsigned char* input, int length); +unsigned char* custom_base64_decode(const char* input, int* output_length); + +//////////////////////////////////////////////////////////////////////////////// +// TERMINAL UI FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Terminal dimension and UI functions +void init_terminal_dimensions(void); +void print_centered_header(const char* text, int pause_before_clear); + +//////////////////////////////////////////////////////////////////////////////// +// MENU SYSTEM FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Interactive menu interface functions +void show_main_menu(void); +int handle_generate_menu(void); +int handle_encrypt_menu(void); +int handle_decrypt_menu(void); +int handle_pads_menu(void); +int handle_text_encrypt(void); +int handle_file_encrypt(void); +int handle_verify_pad(const char* pad_chksum); +int handle_delete_pad(const char* pad_chksum); + +//////////////////////////////////////////////////////////////////////////////// +// ENHANCED INPUT FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Advanced input handling +int get_filename_with_default(const char* prompt, const char* default_path, char* result, size_t result_size); + +//////////////////////////////////////////////////////////////////////////////// +// PAD SELECTION FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Unified pad selection interface +char* select_pad_interactive(const char* title, const char* prompt, pad_filter_type_t filter_type, int allow_cancel); + +//////////////////////////////////////////////////////////////////////////////// +// USAGE AND HELP FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Help and usage display +void print_usage(const char* program_name); + +#endif // OTP_H \ No newline at end of file diff --git a/otp copy.c b/otp copy.c new file mode 100644 index 0000000..b6a847c --- /dev/null +++ b/otp copy.c @@ -0,0 +1,6278 @@ +#define _POSIX_C_SOURCE 200809L +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "nostr_chacha20.h" +#include "otp.h" + +// Custom base64 character set +static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const int base64_decode_table[256] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +}; + +#define MAX_INPUT_SIZE 4096 +#define MAX_LINE_LENGTH 1024 +#define MAX_HASH_LENGTH 65 +#define PROGRESS_UPDATE_INTERVAL (64 * 1024 * 1024) // 64MB intervals +#define DEFAULT_PADS_DIR "pads" +#define FILES_DIR "files" + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// GLOBAL VARIABLES +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +static char current_pads_dir[512] = DEFAULT_PADS_DIR; +static char default_pad_path[1024] = ""; +static int is_interactive_mode = 0; + +// Terminal dimensions +static int terminal_width = 80; // Default fallback width +static int terminal_height = 24; // Default fallback height + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// TERMINAL UI FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +// Initialize terminal dimensions +void init_terminal_dimensions(void) { + struct winsize ws; + + // Try to get actual terminal size + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0 && ws.ws_row > 0) { + terminal_width = ws.ws_col; + terminal_height = ws.ws_row; + } + // If ioctl fails, keep the default values (80x24) +} + +// Print centered header with = padding, screen clearing, and optional pause +void print_centered_header(const char* text, int pause_before_clear) { + if (!text) return; + + // Phase 1: Pause if requested + if (pause_before_clear) { + printf("\nPress Enter to continue..."); + fflush(stdout); + + // Wait for Enter key + int c; + while ((c = getchar()) != '\n' && c != EOF) { + // Consume any extra characters until newline + } + } + + // Phase 2: Clear screen using terminal height + for (int i = 0; i < terminal_height; i++) { + printf("\n"); + } + + // Phase 3: Display centered header (existing logic) + int text_len = strlen(text); + int available_width = terminal_width; + + // Ensure minimum spacing: at least 1 space on each side + int min_required = text_len + 4; // text + " " + text + " " (spaces around text) + + if (available_width < min_required) { + // Terminal too narrow - just print the text with minimal formatting + printf("=== %s ===\n", text); + return; + } + + // Calculate padding + int total_padding = available_width - text_len - 2; // -2 for spaces around text + int left_padding = total_padding / 2; + int right_padding = total_padding - left_padding; + + // Print the header + for (int i = 0; i < left_padding; i++) { + printf("="); + } + printf(" %s ", text); + for (int i = 0; i < right_padding; i++) { + printf("="); + } + printf("\n"); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// MAIN +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int main(int argc, char* argv[]) { + // Initialize terminal dimensions first + init_terminal_dimensions(); + + // Load preferences + load_preferences(); + + // Detect interactive mode: only true when running with no arguments + is_interactive_mode = (argc == 1); + + // Check for OTP thumb drive on startup + char otp_drive_path[512]; + if (detect_otp_thumb_drive(otp_drive_path, sizeof(otp_drive_path))) { + // Only show messages in interactive mode + if (is_interactive_mode) { + printf("Detected OTP thumb drive: %s\n", otp_drive_path); + printf("Using as default pads directory for this session.\n\n"); + } + strncpy(current_pads_dir, otp_drive_path, sizeof(current_pads_dir) - 1); + current_pads_dir[sizeof(current_pads_dir) - 1] = '\0'; + } + + if (is_interactive_mode) { + return interactive_mode(); + } else { + return command_line_mode(argc, argv); + } +} + + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// COMMAND LINE MODE +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int command_line_mode(int argc, char* argv[]) { + // Check for help flags first + if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--h") == 0 || + strcmp(argv[1], "-help") == 0 || strcmp(argv[1], "--help") == 0 || + strcmp(argv[1], "help") == 0) { + print_usage(argv[0]); + return 0; + } + + if (strcmp(argv[1], "generate") == 0 || strcmp(argv[1], "-g") == 0) { + if (argc != 3) { + printf("Usage: %s generate|-g \n", argv[0]); + printf("Size examples: 1024, 1GB, 5TB, 512MB\n"); + return 1; + } + uint64_t size = parse_size_string(argv[2]); + if (size == 0) { + printf("Error: Invalid size format\n"); + return 1; + } + return generate_pad(size, 1); // Use simplified pad generation + } + else if (strcmp(argv[1], "encrypt") == 0 || strcmp(argv[1], "-e") == 0) { + // Check for piped input first + if (has_stdin_data()) { + char* piped_text = read_stdin_text(); + if (piped_text) { + int result = pipe_mode(argc, argv, piped_text); + free(piped_text); + return result; + } + } + + if (argc < 2 || argc > 4) { + printf("Usage: %s encrypt|-e [pad_chksum_or_prefix] [text_to_encrypt]\n", argv[0]); + return 1; + } + + // Check if pad was specified or use default + const char* pad_identifier = NULL; + const char* text = NULL; + + if (argc == 2) { + // Just -e, use default pad, no text (interactive) + pad_identifier = NULL; + text = NULL; + } else if (argc == 3) { + // Could be -e or -e (using default pad) + // Check if default pad is available to determine interpretation + char* default_pad = get_default_pad_path(); + if (default_pad) { + // Default pad available, treat argument as text + pad_identifier = NULL; + text = argv[2]; + free(default_pad); + } else { + // No default pad, treat as pad identifier + pad_identifier = argv[2]; + text = NULL; + } + } else { + // argc == 4: -e + pad_identifier = argv[2]; + text = argv[3]; + } + + // If pad_identifier is NULL, we need to use default pad + if (pad_identifier == NULL) { + char* default_pad = get_default_pad_path(); + if (default_pad) { + // Extract checksum from default pad path + char* filename = strrchr(default_pad, '/'); + if (!filename) filename = default_pad; + else filename++; // Skip the '/' + + // Extract checksum (remove .pad extension) + if (strlen(filename) >= 68 && strstr(filename, ".pad")) { + static char default_checksum[65]; + strncpy(default_checksum, filename, 64); + default_checksum[64] = '\0'; + pad_identifier = default_checksum; + } + free(default_pad); + + // Call encrypt_text and return result + return encrypt_text(pad_identifier, text); + } else { + printf("Error: No default pad configured. Specify pad explicitly or configure default pad.\n"); + return 1; + } + } else { + // Explicit pad specified, normal operation + return encrypt_text(pad_identifier, text); + } + } + else if (strcmp(argv[1], "decrypt") == 0 || strcmp(argv[1], "-d") == 0) { + if (argc == 2) { + // Check for piped input first + if (has_stdin_data()) { + // Piped decrypt mode - read stdin and decrypt silently + char* piped_message = read_stdin_text(); + if (piped_message) { + int result = decrypt_text(NULL, piped_message); + free(piped_message); + return result; + } + } + // Interactive mode - no arguments needed + return decrypt_text(NULL, NULL); + } + else if (argc == 3) { + // Check if the argument looks like an encrypted message (starts with -----) + if (strncmp(argv[2], "-----BEGIN OTP MESSAGE-----", 27) == 0) { + // Inline decrypt with message only - use silent mode for command line + return decrypt_text(NULL, argv[2]); + } else { + // Check if it's a file (contains . or ends with known extensions) + if (strstr(argv[2], ".") != NULL) { + // Treat as file + return decrypt_file(argv[2], NULL); + } else { + // Interactive decrypt with pad hint (legacy support) + return decrypt_text(argv[2], NULL); + } + } + } + else if (argc == 4) { + // Check for -o flag for output file + if (strcmp(argv[2], "-o") == 0) { + printf("Usage: %s decrypt|-d [-o ]\n", argv[0]); + return 1; + } else { + // Legacy format: pad_chksum and message, or file with output + // Use silent mode for command line when message is provided + return decrypt_text(argv[2], argv[3]); + } + } + else if (argc == 5 && strcmp(argv[3], "-o") == 0) { + // File decryption with output: -d -o + return decrypt_file(argv[2], argv[4]); + } + else { + printf("Usage: %s decrypt|-d [encrypted_message|file] [-o output_file]\n", argv[0]); + printf(" %s decrypt|-d [encrypted_message] (pad info from message)\n", argv[0]); + return 1; + } + } + else if (strcmp(argv[1], "-f") == 0) { + // File encryption mode: -f [-a] [-o ] + if (argc < 4) { + printf("Usage: %s -f [-a] [-o ]\n", argv[0]); + return 1; + } + + const char* input_file = argv[2]; + const char* pad_prefix = argv[3]; + int ascii_armor = 0; + const char* output_file = NULL; + + // Parse optional flags + for (int i = 4; i < argc; i++) { + if (strcmp(argv[i], "-a") == 0) { + ascii_armor = 1; + } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { + output_file = argv[++i]; + } + } + + return encrypt_file(pad_prefix, input_file, output_file, ascii_armor); + } + else if (strcmp(argv[1], "list") == 0 || strcmp(argv[1], "-l") == 0) { + printf("Available pads:\n"); + char* selected = select_pad_interactive("Available pads:", "Select pad (or press Enter to exit)", PAD_FILTER_ALL, 0); + if (selected) { + free(selected); + } + return 0; + } + else { + print_usage(argv[0]); + return 1; + } +} + + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// INTERACTIVE MODE +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int interactive_mode(void) { + char input[10]; + printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); + while (1) { + show_main_menu(); + + if (!fgets(input, sizeof(input), stdin)) { + printf("Goodbye!\n"); + break; + } + + char choice = toupper(input[0]); + + switch (choice) { + case 'T': + handle_text_encrypt(); + break; + case 'F': + handle_file_encrypt(); + break; + case 'D': + handle_decrypt_menu(); + break; + case 'P': + handle_pads_menu(); + break; + case 'X': + case 'Q': + printf("Goodbye!\n"); + return 0; + default: + printf("Invalid choice. Please try again.\n"); + break; + } + } + + return 0; +} + + +void show_main_menu(void) { + printf("\n"); + print_centered_header("Main Menu - OTP v0.3.16", 0); + printf("\n"); + + printf(" \033[4mT\033[0mext encrypt\n"); //TEXT ENCRYPT + printf(" \033[4mF\033[0mile encrypt\n"); //FILE ENCRYPT + printf(" \033[4mD\033[0mecrypt\n"); //DECRYPT + printf(" \033[4mP\033[0mads\n"); //PADS + printf(" E\033[4mx\033[0mit\n"); //EXIT + printf("\nSelect option: "); +} + +int handle_generate_menu(void) { + printf("\n"); + print_centered_header("Generate New Pad", 0); + printf("Enter pad size (examples: 1GB, 5TB, 512MB, 2048): "); + + char size_input[64]; + if (!fgets(size_input, sizeof(size_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + size_input[strcspn(size_input, "\n")] = 0; + uint64_t size = parse_size_string(size_input); + + if (size == 0) { + printf("Error: Invalid size format\n"); + return 1; + } + + double size_gb = (double)size / (1024.0 * 1024.0 * 1024.0); + printf("Generating %.2f GB pad...\n", size_gb); + printf("Note: Use 'Add entropy' in Pads menu to enhance randomness after creation.\n"); + + return generate_pad(size, 1); +} + +int handle_encrypt_menu(void) { + printf("\n"); + print_centered_header("Encrypt Data", 0); + + printf("Available pads:\n"); + char* selected = select_pad_interactive("Available pads:", "Select pad (or press Enter to continue)", PAD_FILTER_ALL, 0); + int pad_count = 1; // Assume at least 1 pad if function returned + if (selected) { + free(selected); + } else { + pad_count = 0; + } + if (pad_count == 0) { + printf("No pads available. Generate a pad first.\n"); + return 1; + } + + // Ask user to choose between text and file encryption + printf("\nSelect encryption type:\n"); + printf(" 1. Text message\n"); + printf(" 2. File\n"); + printf("Enter choice (1-2): "); + + char choice_input[10]; + if (!fgets(choice_input, sizeof(choice_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + int choice = atoi(choice_input); + + if (choice == 1) { + // Text encryption - use unified pad selection + char* selected_pad = select_pad_interactive("Select Pad for Text Encryption", + "Select pad (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Text encryption cancelled.\n"); + return 1; + } + + int result = encrypt_text(selected_pad, NULL); // NULL for interactive mode + free(selected_pad); + return result; + } + else if (choice == 2) { + // File encryption + printf("\nFile selection options:\n"); + printf(" 1. Type file path directly\n"); + printf(" 2. Use file manager\n"); + printf("Enter choice (1-2): "); + + char file_choice[10]; + char input_file[512]; + + if (!fgets(file_choice, sizeof(file_choice), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + if (atoi(file_choice) == 2) { + // Use file manager + if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) { + printf("Falling back to manual file path entry.\n"); + printf("Enter input file path: "); + if (!fgets(input_file, sizeof(input_file), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + input_file[strcspn(input_file, "\n")] = 0; + } + } else { + // Direct file path input + printf("Enter input file path: "); + if (!fgets(input_file, sizeof(input_file), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + input_file[strcspn(input_file, "\n")] = 0; + } + + // Check if file exists + if (access(input_file, R_OK) != 0) { + printf("Error: File '%s' not found or cannot be read\n", input_file); + return 1; + } + + // Use unified pad selection + char* selected_pad = select_pad_interactive("Select Pad for File Encryption", + "Select pad (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("File encryption cancelled.\n"); + return 1; + } + + // Ask for output format + printf("\nSelect output format:\n"); + printf(" 1. Binary (.otp) - preserves file permissions\n"); + printf(" 2. ASCII (.otp.asc) - text-safe format\n"); + printf("Enter choice (1-2): "); + + char format_input[10]; + if (!fgets(format_input, sizeof(format_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + int ascii_armor = (atoi(format_input) == 2) ? 1 : 0; + + // Generate default output filename with files directory and use enhanced input function + char default_output[1024]; // Increased size to prevent truncation warnings + char temp_default[1024]; + + // Generate base filename with appropriate extension + if (ascii_armor) { + snprintf(temp_default, sizeof(temp_default), "%s.otp.asc", input_file); + } else { + snprintf(temp_default, sizeof(temp_default), "%s.otp", input_file); + } + + // Apply files directory default path + get_default_file_path(temp_default, default_output, sizeof(default_output)); + + char output_file[512]; + if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { + printf("Error: Failed to read input\n"); + return 1; + } + + const char* output_filename = output_file; + + int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor); + free(selected_pad); + return result; + } + else { + printf("Invalid choice. Please enter 1 or 2.\n"); + return 1; + } +} + +int handle_decrypt_menu(void) { + printf("\n"); + print_centered_header("Smart Decrypt", 0); + printf("Enter encrypted data (paste ASCII armor), file path, or press Enter to browse files:\n"); + + char input_line[MAX_LINE_LENGTH]; + if (!fgets(input_line, sizeof(input_line), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + // Remove newline + input_line[strcspn(input_line, "\n")] = 0; + + if (strlen(input_line) == 0) { + // Empty input - launch file manager to browse for files + char selected_file[512]; + if (launch_file_manager(get_files_directory(), selected_file, sizeof(selected_file)) != 0) { + printf("Error: Could not launch file manager\n"); + return 1; + } + + // Generate smart default output filename with files directory and use enhanced input function + char temp_default[512]; + char default_output[512]; + strncpy(temp_default, selected_file, sizeof(temp_default) - 1); + temp_default[sizeof(temp_default) - 1] = '\0'; + + // Remove common encrypted extensions to get a better default + if (strstr(temp_default, ".otp.asc")) { + // Replace .otp.asc with original extension or no extension + char* ext_pos = strstr(temp_default, ".otp.asc"); + *ext_pos = '\0'; + } else if (strstr(temp_default, ".otp")) { + // Replace .otp with original extension or no extension + char* ext_pos = strstr(temp_default, ".otp"); + *ext_pos = '\0'; + } else { + // No recognized encrypted extension, add .decrypted suffix + strncat(temp_default, ".decrypted", sizeof(temp_default) - strlen(temp_default) - 1); + } + + // Apply files directory default path + get_default_file_path(temp_default, default_output, sizeof(default_output)); + + char output_file[512]; + if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { + printf("Error: Failed to read input\n"); + return 1; + } + + return decrypt_file(selected_file, output_file); + } + else if (strncmp(input_line, "-----BEGIN OTP MESSAGE-----", 27) == 0) { + // Looks like ASCII armor - collect the full message + char full_message[MAX_INPUT_SIZE * 4] = {0}; + strcat(full_message, input_line); + strcat(full_message, "\n"); + + printf("Continue pasting the message (end with -----END OTP MESSAGE-----):\n"); + + char line[MAX_LINE_LENGTH]; + while (fgets(line, sizeof(line), stdin)) { + strncat(full_message, line, sizeof(full_message) - strlen(full_message) - 1); + if (strstr(line, "-----END OTP MESSAGE-----")) { + break; + } + } + + return decrypt_text(NULL, full_message); + } + else { + // Check if it looks like a file path + if (access(input_line, R_OK) == 0) { + // It's a valid file - decrypt it with enhanced input for output filename + char temp_default[512]; + char default_output[512]; + strncpy(temp_default, input_line, sizeof(temp_default) - 1); + temp_default[sizeof(temp_default) - 1] = '\0'; + + // Remove common encrypted extensions to get a better default + if (strstr(temp_default, ".otp.asc")) { + // Replace .otp.asc with original extension or no extension + char* ext_pos = strstr(temp_default, ".otp.asc"); + *ext_pos = '\0'; + } else if (strstr(temp_default, ".otp")) { + // Replace .otp with original extension or no extension + char* ext_pos = strstr(temp_default, ".otp"); + *ext_pos = '\0'; + } else { + // No recognized encrypted extension, add .decrypted suffix + strncat(temp_default, ".decrypted", sizeof(temp_default) - strlen(temp_default) - 1); + } + + // Apply files directory default path + get_default_file_path(temp_default, default_output, sizeof(default_output)); + + char output_file[512]; + if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { + printf("Error: Failed to read input\n"); + return 1; + } + + return decrypt_file(input_line, output_file); + } else { + printf("Input not recognized as ASCII armor or valid file path.\n"); + return 1; + } + } +} + +uint64_t parse_size_string(const char* size_str) { + if (!size_str) return 0; + + char* endptr; + double value = strtod(size_str, &endptr); + + if (value <= 0) return 0; + + // Skip whitespace + while (*endptr && isspace(*endptr)) endptr++; + + uint64_t multiplier = 1; + + if (*endptr) { + char unit[4]; + strncpy(unit, endptr, 3); + unit[3] = '\0'; + + // Convert to uppercase + for (int i = 0; unit[i]; i++) { + unit[i] = toupper(unit[i]); + } + + if (strcmp(unit, "K") == 0 || strcmp(unit, "KB") == 0) { + multiplier = 1024ULL; + } else if (strcmp(unit, "M") == 0 || strcmp(unit, "MB") == 0) { + multiplier = 1024ULL * 1024ULL; + } else if (strcmp(unit, "G") == 0 || strcmp(unit, "GB") == 0) { + multiplier = 1024ULL * 1024ULL * 1024ULL; + } else if (strcmp(unit, "T") == 0 || strcmp(unit, "TB") == 0) { + multiplier = 1024ULL * 1024ULL * 1024ULL * 1024ULL; + } else { + return 0; // Invalid unit + } + } else { + // No unit specified, treat as bytes + multiplier = 1; + } + + return (uint64_t)(value * multiplier); +} + +char* find_pad_by_prefix(const char* prefix) { + DIR* dir = opendir(current_pads_dir); + if (!dir) { + printf("Error: Cannot open pads directory %s\n", current_pads_dir); + return NULL; + } + + struct dirent* entry; + char* matches[100]; // Store up to 100 matches + int match_count = 0; + + // Always try hex prefix matching first + size_t prefix_len = strlen(prefix); + while ((entry = readdir(dir)) != NULL && match_count < 100) { + // Skip . and .. entries, and only process .pad files + if (entry->d_name[0] == '.') continue; + if (!strstr(entry->d_name, ".pad")) continue; + if (strlen(entry->d_name) != 68) continue; // 64 char chksum + ".pad" + + // Compare prefix with the filename (checksum part) + if (strncmp(entry->d_name, prefix, prefix_len) == 0) { + matches[match_count] = malloc(65); + strncpy(matches[match_count], entry->d_name, 64); + matches[match_count][64] = '\0'; + match_count++; + } + } + + // Only prefix matching is supported + + closedir(dir); + + if (match_count == 0) { + printf("No pads found matching '%s'\n", prefix); + printf("Available pads:\n"); + char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0); + if (selected) { + free(selected); + } + return NULL; + } else if (match_count == 1) { + char* result = matches[0]; + return result; + } else { + printf("Multiple matches found for '%s':\n", prefix); + for (int i = 0; i < match_count; i++) { + printf(" %.16s...\n", matches[i]); + } + printf("Please be more specific with your prefix.\n"); + + // Free all matches and return NULL + for (int i = 0; i < match_count; i++) { + free(matches[i]); + } + return NULL; + } +} + + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// PADS MENU +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + + +int show_pad_info(const char* chksum) { + char pad_filename[MAX_HASH_LENGTH + 10]; + char state_filename[MAX_HASH_LENGTH + 10]; + + snprintf(pad_filename, sizeof(pad_filename), "%s.pad", chksum); + snprintf(state_filename, sizeof(state_filename), "%s.state", chksum); + + struct stat st; + if (stat(pad_filename, &st) != 0) { + printf("Pad not found: %s\n", chksum); + return 1; + } + + uint64_t used_bytes; + read_state_offset(chksum, &used_bytes); + + print_centered_header("Pad Information", 0); + printf("ChkSum: %s\n", chksum); + printf("File: %s\n", pad_filename); + + double size_gb = (double)st.st_size / (1024.0 * 1024.0 * 1024.0); + double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0); + double remaining_gb = (double)(st.st_size - used_bytes) / (1024.0 * 1024.0 * 1024.0); + + printf("Total size: %.2f GB (%lu bytes)\n", size_gb, st.st_size); + printf("Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes); + printf("Remaining: %.2f GB (%lu bytes)\n", remaining_gb, st.st_size - used_bytes); + printf("Usage: %.1f%%\n", (double)used_bytes / st.st_size * 100.0); + + return 0; +} + + +/* +// MOVED TO src/util.c +void show_progress(uint64_t current, uint64_t total, time_t start_time) { + time_t now = time(NULL); + double elapsed = difftime(now, start_time); + + if (elapsed < 1.0) elapsed = 1.0; // Avoid division by zero + + double percentage = (double)current / total * 100.0; + double speed = (double)current / elapsed / (1024.0 * 1024.0); // MB/s + + uint64_t remaining_bytes = total - current; + double eta = remaining_bytes / (current / elapsed); + + printf("\rProgress: %.1f%% (%.1f MB/s, ETA: %.0fs) ", percentage, speed, eta); + fflush(stdout); +} +*/ + +int generate_pad(uint64_t size_bytes, int display_progress) { + // Ensure pads directory exists + if (ensure_pads_directory() != 0) { + printf("Error: Cannot create pads directory\n"); + return 1; + } + + char temp_filename[1024]; + char pad_path[MAX_HASH_LENGTH + 20]; + char state_path[MAX_HASH_LENGTH + 20]; + char chksum_hex[MAX_HASH_LENGTH]; + + // Create temporary filename in the pads directory to avoid cross-filesystem issues + snprintf(temp_filename, sizeof(temp_filename), "%s/temp_%ld.pad", current_pads_dir, time(NULL)); + + FILE* urandom = fopen("/dev/urandom", "rb"); + if (!urandom) { + printf("Error: Cannot open /dev/urandom\n"); + return 1; + } + + FILE* pad_file = fopen(temp_filename, "wb"); + if (!pad_file) { + printf("Error: Cannot create temporary pad file %s\n", temp_filename); + fclose(urandom); + return 1; + } + + unsigned char buffer[64 * 1024]; // 64KB buffer + uint64_t bytes_written = 0; + time_t start_time = time(NULL); + + if (display_progress) { + printf("Generating pad...\n"); + } + + while (bytes_written < size_bytes) { + uint64_t chunk_size = sizeof(buffer); + if (size_bytes - bytes_written < chunk_size) { + chunk_size = size_bytes - bytes_written; + } + + if (fread(buffer, 1, (size_t)chunk_size, urandom) != (size_t)chunk_size) { + printf("Error: Failed to read from /dev/urandom\n"); + fclose(urandom); + fclose(pad_file); + unlink(temp_filename); + return 1; + } + + if (fwrite(buffer, 1, (size_t)chunk_size, pad_file) != (size_t)chunk_size) { + printf("Error: Failed to write to pad file\n"); + fclose(urandom); + fclose(pad_file); + unlink(temp_filename); + return 1; + } + + bytes_written += chunk_size; + + if (display_progress && bytes_written % PROGRESS_UPDATE_INTERVAL == 0) { + show_progress(bytes_written, size_bytes, start_time); + } + } + + if (display_progress) { + show_progress(size_bytes, size_bytes, start_time); + printf("\n"); + } + + fclose(urandom); + fclose(pad_file); + + // Calculate XOR checksum of the pad file + if (display_progress) { + printf("Calculating pad checksum...\n"); + } + if (calculate_checksum_with_progress(temp_filename, chksum_hex, display_progress, size_bytes) != 0) { + printf("Error: Cannot calculate pad checksum\n"); + unlink(temp_filename); + return 1; + } + + // Get final paths in pads directory + get_pad_path(chksum_hex, pad_path, state_path); + + // Rename temporary file to final name (atomic operation within same directory) + if (rename(temp_filename, pad_path) != 0) { + printf("Error: Cannot rename temporary pad file to final name\n"); + unlink(temp_filename); + return 1; + } + + // Set pad file to read-only + if (chmod(pad_path, S_IRUSR) != 0) { + printf("Warning: Cannot set pad file to read-only\n"); + } + + // Initialize state file with offset 32 (first 32 bytes reserved for checksum encryption) + FILE* state_file = fopen(state_path, "wb"); + if (state_file) { + uint64_t reserved_bytes = 32; + fwrite(&reserved_bytes, sizeof(uint64_t), 1, state_file); + fclose(state_file); + } else { + printf("Error: Failed to create state file\n"); + unlink(pad_path); + return 1; + } + + double size_gb = (double)size_bytes / (1024.0 * 1024.0 * 1024.0); + printf("Generated pad: %s (%.2f GB)\n", pad_path, size_gb); + printf("Pad checksum: %s\n", chksum_hex); + printf("State file: %s\n", state_path); + printf("Pad file set to read-only\n"); + printf("Use 'Add entropy' in Pads menu to enhance randomness.\n"); + + // Pause before returning to menu to let user see the success message + print_centered_header("Pad Generation Complete", 1); + + return 0; +} + +// In-place pad entropy addition using Chacha20 or direct XOR +int add_entropy_to_pad(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, int display_progress) { + if (!pad_chksum || !entropy_data || entropy_size < 512) { + printf("Error: Invalid entropy data or insufficient entropy\n"); + return 1; + } + + // Get pad file path + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Check if pad exists and get size + struct stat pad_stat; + if (stat(pad_path, &pad_stat) != 0) { + printf("Error: Pad file not found: %s\n", pad_path); + return 1; + } + + uint64_t pad_size = pad_stat.st_size; + + // Determine entropy addition method based on entropy size vs pad size + if (entropy_size >= pad_size) { + // Use direct XOR when entropy >= pad size + return add_entropy_direct_xor(pad_chksum, entropy_data, entropy_size, pad_size, display_progress); + } else { + // Use ChaCha20 when entropy < pad size + return add_entropy_chacha20(pad_chksum, entropy_data, entropy_size, pad_size, display_progress); + } +} + +// Direct XOR entropy addition for large entropy sources +int add_entropy_direct_xor(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, uint64_t pad_size, int display_progress) { + // Get pad file path + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Open pad file for read/write + FILE* pad_file = fopen(pad_path, "r+b"); + if (!pad_file) { + printf("Error: Cannot open pad file for modification: %s\n", pad_path); + printf("Note: Pad files are read-only. Temporarily changing permissions...\n"); + + // Try to make writable temporarily + if (chmod(pad_path, S_IRUSR | S_IWUSR) != 0) { + printf("Error: Cannot change pad file permissions\n"); + return 1; + } + + pad_file = fopen(pad_path, "r+b"); + if (!pad_file) { + printf("Error: Still cannot open pad file for modification\n"); + // Restore read-only + chmod(pad_path, S_IRUSR); + return 1; + } + } + + if (display_progress) { + printf("Adding entropy to pad using direct XOR...\n"); + printf("Pad size: %.2f GB (%lu bytes)\n", (double)pad_size / (1024.0*1024.0*1024.0), pad_size); + printf("Entropy size: %zu bytes\n", entropy_size); + } + + // Process pad in chunks + unsigned char buffer[64 * 1024]; // 64KB chunks + size_t entropy_offset = 0; + uint64_t offset = 0; + time_t start_time = time(NULL); + + while (offset < pad_size) { + size_t chunk_size = sizeof(buffer); + if (pad_size - offset < chunk_size) { + chunk_size = pad_size - offset; + } + + // Read current pad data + if (fread(buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot read pad data at offset %lu\n", offset); + fclose(pad_file); + chmod(pad_path, S_IRUSR); // Restore read-only + return 1; + } + + // XOR with entropy data (wrap around if entropy smaller than pad) + for (size_t i = 0; i < chunk_size; i++) { + buffer[i] ^= entropy_data[entropy_offset % entropy_size]; + entropy_offset++; + } + + // Seek back and write modified data + if (fseek(pad_file, offset, SEEK_SET) != 0) { + printf("Error: Cannot seek to offset %lu\n", offset); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + if (fwrite(buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot write modified pad data\n"); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + offset += chunk_size; + + // Show progress for large pads + if (display_progress && offset % (64 * 1024 * 1024) == 0) { // Every 64MB + show_progress(offset, pad_size, start_time); + } + } + + fclose(pad_file); + + // Restore read-only permissions + if (chmod(pad_path, S_IRUSR) != 0) { + printf("Warning: Cannot restore pad file to read-only\n"); + } + + if (display_progress) { + show_progress(pad_size, pad_size, start_time); + printf("\nāœ“ Entropy successfully added to pad using direct XOR\n"); + printf("āœ“ Pad integrity maintained\n"); + printf("āœ“ %zu bytes of entropy distributed across entire pad\n", entropy_size); + printf("āœ“ Pad restored to read-only mode\n"); + + // Update checksum after entropy addition + printf("\nšŸ”„ Updating pad checksum...\n"); + char new_chksum[65]; + int checksum_result = update_pad_checksum_after_entropy(pad_chksum, new_chksum); + + if (checksum_result == 0) { + printf("āœ“ Pad checksum updated successfully\n"); + printf(" Old checksum: %.16s...\n", pad_chksum); + printf(" New checksum: %.16s...\n", new_chksum); + printf("āœ“ Pad files renamed to new checksum\n"); + + // Pause before returning to menu to let user see the success message + print_centered_header("Entropy Addition Complete", 1); + } else if (checksum_result == 2) { + printf("ℹ Checksum unchanged (unusual but not an error)\n"); + } else { + printf("⚠ Warning: Checksum update failed (entropy was added successfully)\n"); + printf(" You may need to manually handle the checksum update\n"); + return 1; // Report error despite successful entropy addition + } + } + + return 0; +} + +// ChaCha20 entropy addition for smaller entropy sources +int add_entropy_chacha20(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, uint64_t pad_size, int display_progress) { + // Derive Chacha20 key and nonce from entropy + unsigned char key[32], nonce[12]; + if (derive_chacha20_params(entropy_data, entropy_size, key, nonce) != 0) { + printf("Error: Failed to derive Chacha20 parameters from entropy\n"); + return 1; + } + + // Get pad file path + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Open pad file for read/write + FILE* pad_file = fopen(pad_path, "r+b"); + if (!pad_file) { + printf("Error: Cannot open pad file for modification: %s\n", pad_path); + printf("Note: Pad files are read-only. Temporarily changing permissions...\n"); + + // Try to make writable temporarily + if (chmod(pad_path, S_IRUSR | S_IWUSR) != 0) { + printf("Error: Cannot change pad file permissions\n"); + return 1; + } + + pad_file = fopen(pad_path, "r+b"); + if (!pad_file) { + printf("Error: Still cannot open pad file for modification\n"); + // Restore read-only + chmod(pad_path, S_IRUSR); + return 1; + } + } + + if (display_progress) { + printf("Adding entropy to pad using Chacha20...\n"); + printf("Pad size: %.2f GB (%lu bytes)\n", (double)pad_size / (1024.0*1024.0*1024.0), pad_size); + } + + // Process pad in chunks + unsigned char buffer[64 * 1024]; // 64KB chunks + unsigned char keystream[64 * 1024]; + uint64_t offset = 0; + uint32_t counter = 0; + time_t start_time = time(NULL); + + while (offset < pad_size) { + size_t chunk_size = sizeof(buffer); + if (pad_size - offset < chunk_size) { + chunk_size = pad_size - offset; + } + + // Read current pad data + if (fread(buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot read pad data at offset %lu\n", offset); + fclose(pad_file); + chmod(pad_path, S_IRUSR); // Restore read-only + return 1; + } + + // Generate keystream for this chunk + if (chacha20_encrypt(key, counter, nonce, buffer, keystream, chunk_size) != 0) { + printf("Error: Chacha20 keystream generation failed\n"); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + // XOR existing pad with keystream (adds entropy) + for (size_t i = 0; i < chunk_size; i++) { + buffer[i] ^= keystream[i]; + } + + // Seek back and write modified data + if (fseek(pad_file, offset, SEEK_SET) != 0) { + printf("Error: Cannot seek to offset %lu\n", offset); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + if (fwrite(buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot write modified pad data\n"); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + offset += chunk_size; + counter += (chunk_size + 63) / 64; // Round up for block count + + // Show progress for large pads + if (display_progress && offset % (64 * 1024 * 1024) == 0) { // Every 64MB + show_progress(offset, pad_size, start_time); + } + } + + fclose(pad_file); + + // Restore read-only permissions + if (chmod(pad_path, S_IRUSR) != 0) { + printf("Warning: Cannot restore pad file to read-only\n"); + } + + if (display_progress) { + show_progress(pad_size, pad_size, start_time); + printf("\nāœ“ Entropy successfully added to pad using Chacha20\n"); + printf("āœ“ Pad integrity maintained\n"); + printf("āœ“ %zu bytes of entropy distributed across entire pad\n", entropy_size); + printf("āœ“ Pad restored to read-only mode\n"); + + // Update checksum after entropy addition + printf("\nšŸ”„ Updating pad checksum...\n"); + char new_chksum[65]; + int checksum_result = update_pad_checksum_after_entropy(pad_chksum, new_chksum); + + if (checksum_result == 0) { + printf("āœ“ Pad checksum updated successfully\n"); + printf(" Old checksum: %.16s...\n", pad_chksum); + printf(" New checksum: %.16s...\n", new_chksum); + printf("āœ“ Pad files renamed to new checksum\n"); + + // Pause before returning to menu to let user see the success message + print_centered_header("Entropy Addition Complete", 1); + } else if (checksum_result == 2) { + printf("ℹ Checksum unchanged (unusual but not an error)\n"); + } else { + printf("⚠ Warning: Checksum update failed (entropy was added successfully)\n"); + printf(" You may need to manually handle the checksum update\n"); + return 1; // Report error despite successful entropy addition + } + } + + return 0; +} + + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// ENCRYPT AND DECRYPT +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + + +int encrypt_text(const char* pad_identifier, const char* input_text) { + char* pad_chksum = find_pad_by_prefix(pad_identifier); + if (!pad_chksum) { + return 1; + } + + char text_buffer[MAX_INPUT_SIZE]; + char chksum_hex[MAX_HASH_LENGTH]; + uint64_t current_offset; + + char pad_path[MAX_HASH_LENGTH + 20]; + char state_path[MAX_HASH_LENGTH + 20]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Check if pad file exists + if (access(pad_path, R_OK) != 0) { + printf("Error: Pad file %s not found\n", pad_path); + free(pad_chksum); + return 1; + } + + // Read current offset + if (read_state_offset(pad_chksum, ¤t_offset) != 0) { + printf("Error: Cannot read state file\n"); + free(pad_chksum); + return 1; + } + + // Ensure we never encrypt before offset 32 (reserved for checksum encryption) + if (current_offset < 32) { + printf("Warning: State offset below reserved area, adjusting to 32\n"); + current_offset = 32; + if (write_state_offset(pad_chksum, current_offset) != 0) { + printf("Warning: Failed to update state file\n"); + } + } + + // Calculate XOR checksum of pad file + if (calculate_checksum(pad_path, chksum_hex) != 0) { + printf("Error: Cannot calculate pad checksum\n"); + free(pad_chksum); + return 1; + } + + // Get input text - either from parameter or user input + if (input_text != NULL) { + // Use provided text + strncpy(text_buffer, input_text, sizeof(text_buffer) - 1); + text_buffer[sizeof(text_buffer) - 1] = '\0'; + } else { + // Get input text from user (interactive mode) + if (is_interactive_mode) { + printf("\nText input options:\n"); + printf(" 1. Type text directly\n"); + printf(" 2. Use text editor\n"); + printf("Enter choice (1-2): "); + } + + char input_choice[10] = "1"; // Default to direct input in non-interactive mode + if (is_interactive_mode) { + if (!fgets(input_choice, sizeof(input_choice), stdin)) { + printf("Error: Failed to read input\n"); + free(pad_chksum); + return 1; + } + } + + if (is_interactive_mode && atoi(input_choice) == 2) { + // Use text editor + if (launch_text_editor(NULL, text_buffer, sizeof(text_buffer)) != 0) { + if (is_interactive_mode) { + printf("Falling back to direct text input.\n"); + printf("Enter text to encrypt: "); + } + fflush(stdout); + + if (fgets(text_buffer, sizeof(text_buffer), stdin) == NULL) { + printf("Error: Failed to read input\n"); + free(pad_chksum); + return 1; + } + + // Remove newline if present + size_t len = strlen(text_buffer); + if (len > 0 && text_buffer[len - 1] == '\n') { + text_buffer[len - 1] = '\0'; + } + } + } else { + // Direct text input + if (is_interactive_mode) { + printf("Enter text to encrypt: "); + fflush(stdout); + } + + if (fgets(text_buffer, sizeof(text_buffer), stdin) == NULL) { + printf("Error: Failed to read input\n"); + free(pad_chksum); + return 1; + } + + // Remove newline if present + size_t len = strlen(text_buffer); + if (len > 0 && text_buffer[len - 1] == '\n') { + text_buffer[len - 1] = '\0'; + } + } + } + + size_t input_len = strlen(text_buffer); + if (input_len == 0) { + printf("Error: No input provided\n"); + free(pad_chksum); + return 1; + } + + // Check if we have enough pad space + struct stat pad_stat; + if (stat(pad_path, &pad_stat) != 0) { + printf("Error: Cannot get pad file size\n"); + free(pad_chksum); + return 1; + } + + if (current_offset + input_len > (uint64_t)pad_stat.st_size) { + printf("Error: Not enough pad space remaining\n"); + printf("Need: %lu bytes, Available: %lu bytes\n", + input_len, (uint64_t)pad_stat.st_size - current_offset); + free(pad_chksum); + return 1; + } + + // Read pad data at current offset + FILE* pad_file = fopen(pad_path, "rb"); + if (!pad_file) { + printf("Error: Cannot open pad file\n"); + free(pad_chksum); + return 1; + } + + if (fseek(pad_file, current_offset, SEEK_SET) != 0) { + printf("Error: Cannot seek to offset in pad file\n"); + fclose(pad_file); + free(pad_chksum); + return 1; + } + + unsigned char* pad_data = malloc(input_len); + if (fread(pad_data, 1, input_len, pad_file) != input_len) { + printf("Error: Cannot read pad data\n"); + free(pad_data); + fclose(pad_file); + free(pad_chksum); + return 1; + } + fclose(pad_file); + + // Use universal XOR operation for encryption + unsigned char* ciphertext = malloc(input_len); + if (universal_xor_operation((const unsigned char*)text_buffer, input_len, pad_data, ciphertext) != 0) { + printf("Error: Encryption operation failed\n"); + free(pad_data); + free(ciphertext); + free(pad_chksum); + return 1; + } + + // Update state offset + if (write_state_offset(pad_chksum, current_offset + input_len) != 0) { + printf("Warning: Failed to update state file\n"); + } + + // Use universal ASCII armor generator + char* ascii_output; + if (generate_ascii_armor(chksum_hex, current_offset, ciphertext, input_len, &ascii_output) != 0) { + printf("Error: Failed to generate ASCII armor\n"); + free(pad_data); + free(ciphertext); + free(pad_chksum); + return 1; + } + + // Output with appropriate formatting - clean format for piping, spaced format for interactive + int is_interactive = (input_text == NULL); // Interactive if no input_text provided + + if (is_interactive) { + printf("\n\n\n%s\n\n", ascii_output); + } else { + printf("%s\n", ascii_output); // Add newline for proper piping with tee + } + + // Cleanup + free(pad_data); + free(ciphertext); + free(ascii_output); + free(pad_chksum); + + return 0; +} + +int decrypt_text(const char* pad_identifier, const char* encrypted_message) { + // Use universal decrypt function with mode based on global interactive mode detection + (void)pad_identifier; // Suppress unused parameter warning - chksum comes from message + decrypt_mode_t mode = is_interactive_mode ? DECRYPT_MODE_INTERACTIVE : DECRYPT_MODE_SILENT; + return universal_decrypt(encrypted_message, NULL, mode); +} + +int encrypt_file(const char* pad_identifier, const char* input_file, const char* output_file, int ascii_armor) { + char* pad_chksum = find_pad_by_prefix(pad_identifier); + if (!pad_chksum) { + return 1; + } + + char chksum_hex[MAX_HASH_LENGTH]; + uint64_t current_offset; + + char pad_path[MAX_HASH_LENGTH + 20]; + char state_path[MAX_HASH_LENGTH + 20]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Check if input file exists and get its size + struct stat input_stat; + if (stat(input_file, &input_stat) != 0) { + printf("Error: Input file %s not found\n", input_file); + free(pad_chksum); + return 1; + } + + uint64_t file_size = input_stat.st_size; + if (file_size == 0) { + printf("Error: Input file is empty\n"); + free(pad_chksum); + return 1; + } + + // Check if pad file exists + if (access(pad_path, R_OK) != 0) { + printf("Error: Pad file %s not found\n", pad_path); + free(pad_chksum); + return 1; + } + + // Read current offset + if (read_state_offset(pad_chksum, ¤t_offset) != 0) { + printf("Error: Cannot read state file\n"); + free(pad_chksum); + return 1; + } + + // Ensure we never encrypt before offset 32 + if (current_offset < 32) { + printf("Warning: State offset below reserved area, adjusting to 32\n"); + current_offset = 32; + if (write_state_offset(pad_chksum, current_offset) != 0) { + printf("Warning: Failed to update state file\n"); + } + } + + // Calculate XOR checksum of pad file + if (calculate_checksum(pad_path, chksum_hex) != 0) { + printf("Error: Cannot calculate pad checksum\n"); + free(pad_chksum); + return 1; + } + + // Check if we have enough pad space + struct stat pad_stat; + if (stat(pad_path, &pad_stat) != 0) { + printf("Error: Cannot get pad file size\n"); + free(pad_chksum); + return 1; + } + + if (current_offset + file_size > (uint64_t)pad_stat.st_size) { + printf("Error: Not enough pad space remaining\n"); + printf("Need: %lu bytes, Available: %lu bytes\n", + file_size, (uint64_t)pad_stat.st_size - current_offset); + free(pad_chksum); + return 1; + } + + // Generate output filename if not specified, using files directory + char default_output[512]; + if (output_file == NULL) { + char temp_output[512]; + if (ascii_armor) { + snprintf(temp_output, sizeof(temp_output), "%s.otp.asc", input_file); + } else { + snprintf(temp_output, sizeof(temp_output), "%s.otp", input_file); + } + + // Apply files directory default path + get_default_file_path(temp_output, default_output, sizeof(default_output)); + output_file = default_output; + } + + // Open input file + FILE* input_fp = fopen(input_file, "rb"); + if (!input_fp) { + printf("Error: Cannot open input file %s\n", input_file); + free(pad_chksum); + return 1; + } + + // Open pad file + FILE* pad_file = fopen(pad_path, "rb"); + if (!pad_file) { + printf("Error: Cannot open pad file\n"); + fclose(input_fp); + free(pad_chksum); + return 1; + } + + if (fseek(pad_file, current_offset, SEEK_SET) != 0) { + printf("Error: Cannot seek to offset in pad file\n"); + fclose(input_fp); + fclose(pad_file); + free(pad_chksum); + return 1; + } + + // Read and encrypt file + unsigned char buffer[64 * 1024]; + unsigned char pad_buffer[64 * 1024]; + unsigned char* encrypted_data = malloc(file_size); + uint64_t bytes_processed = 0; + time_t start_time = time(NULL); + + printf("Encrypting %s...\n", input_file); + + while (bytes_processed < file_size) { + uint64_t chunk_size = sizeof(buffer); + if (file_size - bytes_processed < chunk_size) { + chunk_size = file_size - bytes_processed; + } + + // Read file data + if (fread(buffer, 1, chunk_size, input_fp) != chunk_size) { + printf("Error: Cannot read input file data\n"); + free(encrypted_data); + fclose(input_fp); + fclose(pad_file); + free(pad_chksum); + return 1; + } + + // Read pad data + if (fread(pad_buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot read pad data\n"); + free(encrypted_data); + fclose(input_fp); + fclose(pad_file); + free(pad_chksum); + return 1; + } + + // Use universal XOR operation for encryption + if (universal_xor_operation(buffer, chunk_size, pad_buffer, &encrypted_data[bytes_processed]) != 0) { + printf("Error: Encryption operation failed\n"); + free(encrypted_data); + fclose(input_fp); + fclose(pad_file); + free(pad_chksum); + return 1; + } + + bytes_processed += chunk_size; + + // Show progress for large files (> 10MB) + if (file_size > 10 * 1024 * 1024 && bytes_processed % (1024 * 1024) == 0) { + show_progress(bytes_processed, file_size, start_time); + } + } + + if (file_size > 10 * 1024 * 1024) { + show_progress(file_size, file_size, start_time); + printf("\n"); + } + + fclose(input_fp); + fclose(pad_file); + + // Write output file + if (ascii_armor) { + // ASCII armored format - same as message format + FILE* output_fp = fopen(output_file, "w"); + if (!output_fp) { + printf("Error: Cannot create output file %s\n", output_file); + free(encrypted_data); + free(pad_chksum); + return 1; + } + + // Use universal ASCII armor generator + char* ascii_output; + if (generate_ascii_armor(chksum_hex, current_offset, encrypted_data, file_size, &ascii_output) != 0) { + printf("Error: Failed to generate ASCII armor\n"); + fclose(output_fp); + free(encrypted_data); + free(pad_chksum); + return 1; + } + + // Write the ASCII armored output to file + fprintf(output_fp, "%s", ascii_output); + + fclose(output_fp); + free(ascii_output); + } else { + // Binary format + FILE* output_fp = fopen(output_file, "wb"); + if (!output_fp) { + printf("Error: Cannot create output file %s\n", output_file); + free(encrypted_data); + free(pad_chksum); + return 1; + } + + // Write binary header + // Magic: "OTP\0" + fwrite("OTP\0", 1, 4, output_fp); + + // Version: 2 bytes + uint16_t version = 1; + fwrite(&version, sizeof(uint16_t), 1, output_fp); + + // Pad checksum: 32 bytes (binary) + unsigned char pad_chksum_bin[32]; + for (int i = 0; i < 32; i++) { + sscanf(chksum_hex + i*2, "%2hhx", &pad_chksum_bin[i]); + } + fwrite(pad_chksum_bin, 1, 32, output_fp); + + // Pad offset: 8 bytes + fwrite(¤t_offset, sizeof(uint64_t), 1, output_fp); + + + // File mode: 4 bytes + uint32_t file_mode = input_stat.st_mode; + fwrite(&file_mode, sizeof(uint32_t), 1, output_fp); + + // File size: 8 bytes + fwrite(&file_size, sizeof(uint64_t), 1, output_fp); + + // Encrypted data + fwrite(encrypted_data, 1, file_size, output_fp); + + fclose(output_fp); + } + + // Update state offset + if (write_state_offset(pad_chksum, current_offset + file_size) != 0) { + printf("Warning: Failed to update state file\n"); + } + + printf("File encrypted successfully: %s\n", output_file); + if (ascii_armor) { + printf("Format: ASCII armored (.otp.asc)\n"); + } else { + printf("Format: Binary (.otp)\n"); + } + + // Pause before returning to menu to let user see the success message + print_centered_header("File Encryption Complete", 1); + + // Cleanup + free(encrypted_data); + free(pad_chksum); + + return 0; +} + +int decrypt_file(const char* input_file, const char* output_file) { + // Check if input file exists + if (access(input_file, R_OK) != 0) { + printf("Error: Input file %s not found\n", input_file); + return 1; + } + + FILE* input_fp = fopen(input_file, "rb"); + if (!input_fp) { + printf("Error: Cannot open input file %s\n", input_file); + return 1; + } + + // Read first few bytes to determine format + char magic[4]; + if (fread(magic, 1, 4, input_fp) != 4) { + printf("Error: Cannot read file header\n"); + fclose(input_fp); + return 1; + } + + fseek(input_fp, 0, SEEK_SET); // Reset to beginning + + if (memcmp(magic, "OTP\0", 4) == 0) { + // Binary format + return decrypt_binary_file(input_fp, output_file); + } else { + // Assume ASCII armored format, read entire file as text + fclose(input_fp); + return decrypt_ascii_file(input_file, output_file); + } +} + +int decrypt_binary_file(FILE* input_fp, const char* output_file) { + // Read binary header + char magic[4]; + uint16_t version; + unsigned char pad_chksum_bin[32]; + uint64_t pad_offset; + uint32_t file_mode; + uint64_t file_size; + + if (fread(magic, 1, 4, input_fp) != 4 || + fread(&version, sizeof(uint16_t), 1, input_fp) != 1 || + fread(pad_chksum_bin, 1, 32, input_fp) != 32 || + fread(&pad_offset, sizeof(uint64_t), 1, input_fp) != 1 || + fread(&file_mode, sizeof(uint32_t), 1, input_fp) != 1 || + fread(&file_size, sizeof(uint64_t), 1, input_fp) != 1) { + printf("Error: Cannot read binary header\n"); + fclose(input_fp); + return 1; + } + + if (memcmp(magic, "OTP\0", 4) != 0) { + printf("Error: Invalid binary format\n"); + fclose(input_fp); + return 1; + } + + // Convert binary checksum to hex + char pad_chksum_hex[65]; + for (int i = 0; i < 32; i++) { + sprintf(pad_chksum_hex + i*2, "%02x", pad_chksum_bin[i]); + } + pad_chksum_hex[64] = '\0'; + + printf("Decrypting binary file...\n"); + printf("File size: %lu bytes\n", file_size); + + // Check if we have the required pad + char pad_path[MAX_HASH_LENGTH + 20]; + char state_path[MAX_HASH_LENGTH + 20]; + get_pad_path(pad_chksum_hex, pad_path, state_path); + + if (access(pad_path, R_OK) != 0) { + printf("Error: Required pad not found: %s\n", pad_chksum_hex); + printf("Available pads:\n"); + char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0); + if (selected) { + free(selected); + } + fclose(input_fp); + return 1; + } + + // Determine output filename + char default_output[512]; + if (output_file == NULL) { + snprintf(default_output, sizeof(default_output), "decrypted.bin"); + output_file = default_output; + } + + // Read encrypted data + unsigned char* encrypted_data = malloc(file_size); + if (fread(encrypted_data, 1, file_size, input_fp) != file_size) { + printf("Error: Cannot read encrypted data\n"); + free(encrypted_data); + fclose(input_fp); + return 1; + } + fclose(input_fp); + + // Open pad file and decrypt + FILE* pad_file = fopen(pad_path, "rb"); + if (!pad_file) { + printf("Error: Cannot open pad file\n"); + free(encrypted_data); + return 1; + } + + if (fseek(pad_file, pad_offset, SEEK_SET) != 0) { + printf("Error: Cannot seek to offset in pad file\n"); + free(encrypted_data); + fclose(pad_file); + return 1; + } + + unsigned char* pad_data = malloc(file_size); + if (fread(pad_data, 1, file_size, pad_file) != file_size) { + printf("Error: Cannot read pad data\n"); + free(encrypted_data); + free(pad_data); + fclose(pad_file); + return 1; + } + fclose(pad_file); + + // Use universal XOR operation for decryption + if (universal_xor_operation(encrypted_data, file_size, pad_data, encrypted_data) != 0) { + printf("Error: Decryption operation failed\n"); + free(encrypted_data); + free(pad_data); + return 1; + } + + // Write decrypted file + FILE* output_fp = fopen(output_file, "wb"); + if (!output_fp) { + printf("Error: Cannot create output file %s\n", output_file); + free(encrypted_data); + free(pad_data); + return 1; + } + + if (fwrite(encrypted_data, 1, file_size, output_fp) != file_size) { + printf("Error: Cannot write decrypted data\n"); + free(encrypted_data); + free(pad_data); + fclose(output_fp); + return 1; + } + fclose(output_fp); + + // Restore file permissions + if (chmod(output_file, file_mode) != 0) { + printf("Warning: Cannot restore file permissions\n"); + } + + printf("File decrypted successfully: %s\n", output_file); + printf("Restored permissions and metadata\n"); + + // Pause before returning to menu to let user see the success message + print_centered_header("File Decryption Complete", 1); + + // Cleanup + free(encrypted_data); + free(pad_data); + + return 0; +} + +int decrypt_ascii_file(const char* input_file, const char* output_file) { + // Use universal decrypt function with file-to-file mode + return universal_decrypt(input_file, output_file, DECRYPT_MODE_FILE_TO_FILE); +} + +int read_state_offset(const char* pad_chksum, uint64_t* offset) { + char state_filename[1024]; + snprintf(state_filename, sizeof(state_filename), "%s/%s.state", current_pads_dir, pad_chksum); + + FILE* state_file = fopen(state_filename, "rb"); + if (!state_file) { + *offset = 0; + return 0; + } + + if (fread(offset, sizeof(uint64_t), 1, state_file) != 1) { + fclose(state_file); + *offset = 0; + return 0; + } + + fclose(state_file); + return 0; +} + +int write_state_offset(const char* pad_chksum, uint64_t offset) { + char state_filename[1024]; + snprintf(state_filename, sizeof(state_filename), "%s/%s.state", current_pads_dir, pad_chksum); + + FILE* state_file = fopen(state_filename, "wb"); + if (!state_file) { + return 1; + } + + if (fwrite(&offset, sizeof(uint64_t), 1, state_file) != 1) { + fclose(state_file); + return 1; + } + + fclose(state_file); + return 0; +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// DIRECTORY MANAGEMENT FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int ensure_pads_directory(void) { + struct stat st = {0}; + if (stat(current_pads_dir, &st) == -1) { + if (mkdir(current_pads_dir, 0755) != 0) { + return 1; + } + } + return 0; +} + +const char* get_files_directory(void) { + struct stat st = {0}; + if (stat(FILES_DIR, &st) == 0 && S_ISDIR(st.st_mode)) { + return FILES_DIR; + } + return "."; // Fall back to current directory +} + +void get_default_file_path(const char* filename, char* result_path, size_t result_size) { + const char* files_dir = get_files_directory(); + + // If filename already has a path (contains '/'), use it as-is + if (strchr(filename, '/') != NULL) { + strncpy(result_path, filename, result_size - 1); + result_path[result_size - 1] = '\0'; + return; + } + + // Otherwise, prepend the files directory + snprintf(result_path, result_size, "%s/%s", files_dir, filename); +} + +void get_directory_display(const char* file_path, char* result, size_t result_size) { + // Extract directory path from full file path + char dir_path[512]; + char* last_slash = strrchr(file_path, '/'); + + if (last_slash) { + size_t dir_len = last_slash - file_path; + if (dir_len >= sizeof(dir_path)) { + dir_len = sizeof(dir_path) - 1; + } + strncpy(dir_path, file_path, dir_len); + dir_path[dir_len] = '\0'; + } else { + // No directory separator, assume current directory + strcpy(dir_path, "."); + } + + // USB Drive Detection and Smart Shortening + char* home_dir = getenv("HOME"); + + // Check for USB/removable media mount patterns + if (strstr(dir_path, "/media/") || strstr(dir_path, "/run/media/") || strstr(dir_path, "/mnt/")) { + // Extract USB label/name + char* media_start = NULL; + if (strstr(dir_path, "/media/")) { + media_start = strstr(dir_path, "/media/"); + } else if (strstr(dir_path, "/run/media/")) { + media_start = strstr(dir_path, "/run/media/"); + } else if (strstr(dir_path, "/mnt/")) { + media_start = strstr(dir_path, "/mnt/"); + } + + if (media_start) { + // Find the USB label part + char* path_after_media = strchr(media_start + 1, '/'); + if (path_after_media) { + path_after_media++; // Skip the slash + + // For /media/user/LABEL pattern, skip the username to get to the drive label + if (strstr(media_start, "/media/")) { + char* next_slash = strchr(path_after_media, '/'); + if (next_slash) { + path_after_media = next_slash + 1; + } + } + // For /run/media/user/LABEL pattern, skip the username + else if (strstr(media_start, "/run/media/")) { + char* next_slash = strchr(path_after_media, '/'); + if (next_slash) { + path_after_media = next_slash + 1; + } + } + + // Extract just the USB label (up to next slash or end) + char* label_end = strchr(path_after_media, '/'); + char usb_label[32]; + if (label_end) { + size_t label_len = label_end - path_after_media; + if (label_len > sizeof(usb_label) - 1) label_len = sizeof(usb_label) - 1; + strncpy(usb_label, path_after_media, label_len); + usb_label[label_len] = '\0'; + } else { + // USB label is the last part + strncpy(usb_label, path_after_media, sizeof(usb_label) - 1); + usb_label[sizeof(usb_label) - 1] = '\0'; + } + + // Format with USB: prefix, limiting total length to fit in result + snprintf(result, result_size, "USB:%s", usb_label); + // Truncate if too long + if (strlen(result) > 11) { + result[11] = '\0'; + } + return; + } + } + } + + // Home directory shortening + if (home_dir && strncmp(dir_path, home_dir, strlen(home_dir)) == 0) { + if (dir_path[strlen(home_dir)] == '/' || dir_path[strlen(home_dir)] == '\0') { + // Replace home directory with ~ + char temp[512]; + snprintf(temp, sizeof(temp), "~%s", dir_path + strlen(home_dir)); + + // If result is too long, truncate intelligently + if (strlen(temp) > 11) { + // Show ~/...end_part + char* last_part = strrchr(temp, '/'); + if (last_part && strlen(last_part) < 8) { + snprintf(result, result_size, "~...%s", last_part); + } else { + strncpy(result, temp, 11); + result[11] = '\0'; + } + } else { + strncpy(result, temp, result_size - 1); + result[result_size - 1] = '\0'; + } + return; + } + } + + // Current working directory + if (strcmp(dir_path, ".") == 0 || strcmp(dir_path, current_pads_dir) == 0) { + strncpy(result, "pads", result_size - 1); + result[result_size - 1] = '\0'; + return; + } + + // System/other paths - smart truncation with ellipsis + if (strlen(dir_path) > 11) { + // Try to show the most meaningful part + char* last_part = strrchr(dir_path, '/'); + if (last_part && strlen(last_part) < 9) { + // Show .../last_part + snprintf(result, result_size, "...%s", last_part); + } else { + // Show first part with ellipsis + strncpy(result, dir_path, 8); + strncpy(result + 8, "...", result_size - 8 - 1); + result[result_size - 1] = '\0'; + } + } else { + // Short enough, use as-is + strncpy(result, dir_path, result_size - 1); + result[result_size - 1] = '\0'; + } +} + +void get_pad_path(const char* chksum, char* pad_path, char* state_path) { + snprintf(pad_path, 1024, "%s/%s.pad", current_pads_dir, chksum); + snprintf(state_path, 1024, "%s/%s.state", current_pads_dir, chksum); +} + +// Stdin detection functions implementation +int has_stdin_data(void) { + // Check if stdin is a pipe/redirect (not a terminal) + if (!isatty(STDIN_FILENO)) { + return 1; + } + return 0; +} + +char* read_stdin_text(void) { + size_t capacity = 4096; + size_t length = 0; + char* buffer = malloc(capacity); + + if (!buffer) { + return NULL; + } + + char chunk[1024]; + while (fgets(chunk, sizeof(chunk), stdin)) { + size_t chunk_len = strlen(chunk); + + // Ensure we have enough capacity + while (length + chunk_len >= capacity) { + capacity *= 2; + char* new_buffer = realloc(buffer, capacity); + if (!new_buffer) { + free(buffer); + return NULL; + } + buffer = new_buffer; + } + + strcpy(buffer + length, chunk); + length += chunk_len; + } + + // Remove trailing newline if present + if (length > 0 && buffer[length - 1] == '\n') { + buffer[length - 1] = '\0'; + length--; + } + + // If empty, free and return NULL + if (length == 0) { + free(buffer); + return NULL; + } + + return buffer; +} + +int pipe_mode(int argc, char* argv[], const char* piped_text) { + (void)argc; // Suppress unused parameter warning + (void)argv; // Suppress unused parameter warning + + // Check if we have a default pad configured + char* default_pad = get_default_pad_path(); + if (default_pad) { + // Verify the default pad exists and extract checksum + if (access(default_pad, R_OK) == 0) { + // Extract checksum from pad filename + char* filename = strrchr(default_pad, '/'); + if (!filename) filename = default_pad; + else filename++; // Skip the '/' + + // Extract checksum (remove .pad extension) + if (strlen(filename) >= 68 && strstr(filename, ".pad")) { + char pad_checksum[65]; + strncpy(pad_checksum, filename, 64); + pad_checksum[64] = '\0'; + + free(default_pad); + + // Encrypt using the default pad (silent mode) + return encrypt_text(pad_checksum, piped_text); + } + } + + fprintf(stderr, "Error: Default pad not found or invalid: %s\n", default_pad); + free(default_pad); + return 1; + } + + fprintf(stderr, "Error: No default pad configured for pipe mode\n"); + fprintf(stderr, "Configure a default pad in ~/.otp/otp.conf\n"); + return 1; +} + +// Preferences management functions implementation +int load_preferences(void) { + char* home_dir = getenv("HOME"); + if (!home_dir) { + return 1; // No home directory + } + + char preferences_dir[1024]; + char preferences_file[2048]; // Increased buffer size to accommodate longer paths + snprintf(preferences_dir, sizeof(preferences_dir), "%s/.otp", home_dir); + snprintf(preferences_file, sizeof(preferences_file), "%s/otp.conf", preferences_dir); + + FILE* file = fopen(preferences_file, "r"); + if (!file) { + // No preferences file exists - create it and set first pad as default + + // Create .otp directory if it doesn't exist + struct stat st = {0}; + if (stat(preferences_dir, &st) == -1) { + if (mkdir(preferences_dir, 0755) != 0) { + return 1; + } + } + + // Find the first available pad to set as default + DIR* dir = opendir(current_pads_dir); + if (dir) { + struct dirent* entry; + char first_pad_path[1024]; + int found_pad = 0; + + while ((entry = readdir(dir)) != NULL && !found_pad) { + if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) { + // Found a pad file - construct full absolute path + if (current_pads_dir[0] == '/') { + // Already absolute path + int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s", current_pads_dir, entry->d_name); + if (ret >= (int)sizeof(first_pad_path)) { + // Path was truncated, skip this entry + continue; + } + } else { + // Relative path - make it absolute + char current_dir[512]; + if (getcwd(current_dir, sizeof(current_dir))) { + int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s/%s", current_dir, current_pads_dir, entry->d_name); + if (ret >= (int)sizeof(first_pad_path)) { + // Path was truncated, skip this entry + continue; + } + } else { + // Fallback to relative path + int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s", current_pads_dir, entry->d_name); + if (ret >= (int)sizeof(first_pad_path)) { + // Path was truncated, skip this entry + continue; + } + } + } + strncpy(default_pad_path, first_pad_path, sizeof(default_pad_path) - 1); + default_pad_path[sizeof(default_pad_path) - 1] = '\0'; + found_pad = 1; + } + } + closedir(dir); + + // Create the preferences file with the default pad + if (found_pad) { + save_preferences(); + } + } + + return 0; // Successfully initialized + } + + char line[1024]; + while (fgets(line, sizeof(line), file)) { + // Remove newline + line[strcspn(line, "\n")] = 0; + + // Skip empty lines and comments + if (strlen(line) == 0 || line[0] == '#') { + continue; + } + + // Parse key=value pairs + char* equals = strchr(line, '='); + if (equals) { + *equals = '\0'; + char* key = line; + char* value = equals + 1; + + // Trim whitespace + while (*key == ' ' || *key == '\t') key++; + while (*value == ' ' || *value == '\t') value++; + + if (strcmp(key, "default_pad") == 0) { + strncpy(default_pad_path, value, sizeof(default_pad_path) - 1); + default_pad_path[sizeof(default_pad_path) - 1] = '\0'; + } + } + } + + fclose(file); + return 0; +} + +int save_preferences(void) { + char* home_dir = getenv("HOME"); + if (!home_dir) { + return 1; + } + + char preferences_dir[1024]; + char preferences_file[2048]; // Increased buffer size to accommodate longer paths + snprintf(preferences_dir, sizeof(preferences_dir), "%s/.otp", home_dir); + snprintf(preferences_file, sizeof(preferences_file), "%s/otp.conf", preferences_dir); + + // Create .otp directory if it doesn't exist + struct stat st = {0}; + if (stat(preferences_dir, &st) == -1) { + if (mkdir(preferences_dir, 0755) != 0) { + return 1; + } + } + + FILE* file = fopen(preferences_file, "w"); + if (!file) { + return 1; + } + + fprintf(file, "# OTP Preferences File\n"); + fprintf(file, "# This file is automatically generated and updated by the OTP program\n\n"); + + if (strlen(default_pad_path) > 0) { + fprintf(file, "default_pad=%s\n", default_pad_path); + } + + fclose(file); + return 0; +} + +char* get_preference(const char* key) { + if (strcmp(key, "default_pad") == 0) { + if (strlen(default_pad_path) > 0) { + return strdup(default_pad_path); + } + } + return NULL; +} + +int set_preference(const char* key, const char* value) { + if (strcmp(key, "default_pad") == 0) { + if (value) { + strncpy(default_pad_path, value, sizeof(default_pad_path) - 1); + default_pad_path[sizeof(default_pad_path) - 1] = '\0'; + } else { + default_pad_path[0] = '\0'; + } + return save_preferences(); + } + return 1; +} + +char* get_default_pad_path(void) { + if (strlen(default_pad_path) > 0) { + return strdup(default_pad_path); + } + return NULL; +} + +int set_default_pad_path(const char* pad_path) { + if (!pad_path) { + return set_preference("default_pad", NULL); + } + + // Ensure we store the full absolute path + char absolute_path[1024]; + if (pad_path[0] == '/') { + // Already absolute path + strncpy(absolute_path, pad_path, sizeof(absolute_path) - 1); + absolute_path[sizeof(absolute_path) - 1] = '\0'; + } else { + // Relative path - make it absolute + char current_dir[512]; + if (getcwd(current_dir, sizeof(current_dir))) { + snprintf(absolute_path, sizeof(absolute_path), "%s/%s", current_dir, pad_path); + } else { + // Fallback to using the path as-is if getcwd fails + strncpy(absolute_path, pad_path, sizeof(absolute_path) - 1); + absolute_path[sizeof(absolute_path) - 1] = '\0'; + } + } + + return set_preference("default_pad", absolute_path); +} + +// OTP thumb drive detection function implementation +int detect_otp_thumb_drive(char* otp_drive_path, size_t path_size) { + const char* mount_dirs[] = {"/media", "/run/media", "/mnt", NULL}; + + for (int mount_idx = 0; mount_dirs[mount_idx] != NULL; mount_idx++) { + DIR* mount_dir = opendir(mount_dirs[mount_idx]); + if (!mount_dir) continue; + + struct dirent* mount_entry; + while ((mount_entry = readdir(mount_dir)) != NULL) { + if (mount_entry->d_name[0] == '.') continue; + + char mount_path[1024]; // Increased buffer size + snprintf(mount_path, sizeof(mount_path), "%s/%s", mount_dirs[mount_idx], mount_entry->d_name); + + // For /media, we need to go one level deeper (user directories) + if (strcmp(mount_dirs[mount_idx], "/media") == 0) { + // This is /media/[username] - look inside for drives + DIR* user_dir = opendir(mount_path); + if (!user_dir) continue; + + struct dirent* user_entry; + while ((user_entry = readdir(user_dir)) != NULL) { + if (user_entry->d_name[0] == '.') continue; + + // Check if drive name starts with "OTP" + if (strncmp(user_entry->d_name, "OTP", 3) != 0) continue; + + char user_mount_path[2048]; // Increased buffer size + // Verify buffer has enough space before concatenation + size_t mount_len = strlen(mount_path); + size_t entry_len = strlen(user_entry->d_name); + if (mount_len + entry_len + 2 < sizeof(user_mount_path)) { + snprintf(user_mount_path, sizeof(user_mount_path), "%s/%s", mount_path, user_entry->d_name); + + // Check if this is a readable directory + DIR* drive_dir = opendir(user_mount_path); + if (drive_dir) { + closedir(drive_dir); + strncpy(otp_drive_path, user_mount_path, path_size - 1); + otp_drive_path[path_size - 1] = '\0'; + closedir(user_dir); + closedir(mount_dir); + return 1; // Found OTP drive + } + } + } + closedir(user_dir); + } else if (strcmp(mount_dirs[mount_idx], "/run/media") == 0) { + // For /run/media, we need to go one level deeper (skip username) + DIR* user_dir = opendir(mount_path); + if (!user_dir) continue; + + struct dirent* user_entry; + while ((user_entry = readdir(user_dir)) != NULL) { + if (user_entry->d_name[0] == '.') continue; + + // Check if drive name starts with "OTP" + if (strncmp(user_entry->d_name, "OTP", 3) != 0) continue; + + char user_mount_path[2048]; // Increased buffer size + snprintf(user_mount_path, sizeof(user_mount_path), "%s/%s", mount_path, user_entry->d_name); + + // Check if this is a readable directory + DIR* drive_dir = opendir(user_mount_path); + if (drive_dir) { + closedir(drive_dir); + strncpy(otp_drive_path, user_mount_path, path_size - 1); + otp_drive_path[path_size - 1] = '\0'; + closedir(user_dir); + closedir(mount_dir); + return 1; // Found OTP drive + } + } + closedir(user_dir); + } else { + // Direct mount point (like /mnt/OTP_DRIVE) + // Check if drive name starts with "OTP" + if (strncmp(mount_entry->d_name, "OTP", 3) == 0) { + DIR* drive_dir = opendir(mount_path); + if (drive_dir) { + closedir(drive_dir); + strncpy(otp_drive_path, mount_path, path_size - 1); + otp_drive_path[path_size - 1] = '\0'; + closedir(mount_dir); + return 1; // Found OTP drive + } + } + } + } + closedir(mount_dir); + } + + return 0; // No OTP drive found +} + + + + +// Custom base64 encode function + +char* custom_base64_encode(const unsigned char* input, int length) { + int output_length = 4 * ((length + 2) / 3); + char* encoded = malloc(output_length + 1); + if (!encoded) return NULL; + + int i, j; + for (i = 0, j = 0; i < length;) { + uint32_t octet_a = i < length ? input[i++] : 0; + uint32_t octet_b = i < length ? input[i++] : 0; + uint32_t octet_c = i < length ? input[i++] : 0; + + uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c; + + encoded[j++] = base64_chars[(triple >> 18) & 63]; + encoded[j++] = base64_chars[(triple >> 12) & 63]; + encoded[j++] = base64_chars[(triple >> 6) & 63]; + encoded[j++] = base64_chars[triple & 63]; + } + + // Add padding + for (int pad = 0; pad < (3 - length % 3) % 3; pad++) { + encoded[output_length - 1 - pad] = '='; + } + + encoded[output_length] = '\0'; + return encoded; +} + +// Custom base64 decode function +unsigned char* custom_base64_decode(const char* input, int* output_length) { + int input_length = strlen(input); + if (input_length % 4 != 0) return NULL; + + *output_length = input_length / 4 * 3; + if (input[input_length - 1] == '=') (*output_length)--; + if (input[input_length - 2] == '=') (*output_length)--; + + unsigned char* decoded = malloc(*output_length); + if (!decoded) return NULL; + + int i, j; + for (i = 0, j = 0; i < input_length;) { + int sextet_a = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; + int sextet_b = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; + int sextet_c = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; + int sextet_d = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; + + if (sextet_a == -1 || sextet_b == -1 || sextet_c == -1 || sextet_d == -1) { + free(decoded); + return NULL; + } + + uint32_t triple = (sextet_a << 18) + (sextet_b << 12) + (sextet_c << 6) + sextet_d; + + if (j < *output_length) decoded[j++] = (triple >> 16) & 255; + if (j < *output_length) decoded[j++] = (triple >> 8) & 255; + if (j < *output_length) decoded[j++] = triple & 255; + } + + return decoded; +} + + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// ADD ENTROPY +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// TRUERNG DEVICE DETECTION AND COMMUNICATION +// Ported from true_rng/main.c for entropy collection +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +// Read USB device info from sysfs (ported from TrueRNG reference) +int read_usb_device_info(const char* port_name, char* vid, char* pid) { + char path[512]; + FILE *fp; + + // Try to read idVendor first (works for both ttyUSB and ttyACM devices) + snprintf(path, sizeof(path), "/sys/class/tty/%s/device/../idVendor", port_name); + fp = fopen(path, "r"); + if (fp) { + if (fgets(vid, 8, fp) != NULL) { + // Remove newline if present + int len = strlen(vid); + if (len > 0 && vid[len-1] == '\n') { + vid[len-1] = '\0'; + } + } else { + fclose(fp); + return 0; + } + fclose(fp); + } else { + return 0; + } + + // Try to read idProduct + snprintf(path, sizeof(path), "/sys/class/tty/%s/device/../idProduct", port_name); + fp = fopen(path, "r"); + if (fp) { + if (fgets(pid, 8, fp) != NULL) { + // Remove newline if present + int len = strlen(pid); + if (len > 0 && pid[len-1] == '\n') { + pid[len-1] = '\0'; + } + } else { + fclose(fp); + return 0; + } + fclose(fp); + return 1; + } else { + return 0; + } +} + +// Detect all available hardware RNG devices (TrueRNG and SwiftRNG) +int detect_all_hardware_rng_devices(hardware_rng_device_t* devices, int max_devices, int* num_devices_found) { + DIR *dir; + struct dirent *entry; + char vid[8], pid[8]; + int device_count = 0; + + *num_devices_found = 0; + + dir = opendir("/dev"); + if (dir == NULL) { + return 1; // Error opening /dev directory + } + + while ((entry = readdir(dir)) != NULL && device_count < max_devices) { + // Look for ttyUSB* or ttyACM* devices + if (strncmp(entry->d_name, "ttyUSB", 6) == 0 || + strncmp(entry->d_name, "ttyACM", 6) == 0) { + + if (read_usb_device_info(entry->d_name, vid, pid)) { + // Convert to uppercase for comparison + for (int i = 0; vid[i]; i++) vid[i] = toupper(vid[i]); + for (int i = 0; pid[i]; i++) pid[i] = toupper(pid[i]); + + truerng_device_type_t device_type = 0; + + // Check for TrueRNGproV2 / SwiftRNGproV2 + if (strcmp(vid, TRUERNGPROV2_VID) == 0 && strcmp(pid, TRUERNGPROV2_PID) == 0) { + device_type = TRUERNG_PRO_V2; + } + // Check for TrueRNGpro / SwiftRNGpro + else if (strcmp(vid, TRUERNGPRO_VID) == 0 && strcmp(pid, TRUERNGPRO_PID) == 0) { + device_type = TRUERNG_PRO; + } + // Check for TrueRNG / SwiftRNG + else if (strcmp(vid, TRUERNG_VID) == 0 && strcmp(pid, TRUERNG_PID) == 0) { + device_type = TRUERNG_ORIGINAL; + } + + if (device_type != 0) { + // Found a valid device - store it + snprintf(devices[device_count].port_path, sizeof(devices[device_count].port_path), + "/dev/%s", entry->d_name); + devices[device_count].device_type = device_type; + strncpy(devices[device_count].friendly_name, + get_truerng_device_name(device_type), + sizeof(devices[device_count].friendly_name) - 1); + devices[device_count].friendly_name[sizeof(devices[device_count].friendly_name) - 1] = '\0'; + devices[device_count].is_working = 0; // Will be tested later + + device_count++; + } + } + } + } + + closedir(dir); + *num_devices_found = device_count; + return 0; // Success +} + +// Test a hardware RNG device to verify it's working +int test_hardware_rng_device(const hardware_rng_device_t* device) { + int serial_fd = -1; + int test_result = 0; + + // Setup serial port + serial_fd = setup_truerng_serial_port(device->port_path); + if (serial_fd < 0) { + return 0; // Device not working - cannot open serial port + } + + // Try to read a small amount of data to test functionality + unsigned char test_buffer[64]; + size_t bytes_read = 0; + + // Set a short timeout for testing + struct termios tty; + if (tcgetattr(serial_fd, &tty) == 0) { + tty.c_cc[VMIN] = 0; + tty.c_cc[VTIME] = 1; // 0.1 second timeout + tcsetattr(serial_fd, TCSANOW, &tty); + } + + // Try to read test data + ssize_t result = read(serial_fd, test_buffer, sizeof(test_buffer)); + if (result > 0) { + bytes_read = result; + } + + close(serial_fd); + + // Device is working if we got at least some data + if (bytes_read > 0) { + test_result = 1; + } + + return test_result; +} + +// Interactive device selection menu +int select_hardware_rng_device_interactive(hardware_rng_device_t* devices, int num_devices, hardware_rng_device_t* selected_device) { + if (num_devices == 0) { + printf("No hardware RNG devices found.\n"); + return 1; // No devices available + } + + if (num_devices == 1) { + // Only one device - use it automatically + *selected_device = devices[0]; + printf("Found 1 hardware RNG device: %s at %s\n", + selected_device->friendly_name, selected_device->port_path); + return 0; + } + + // Multiple devices - show menu + printf("\nMultiple hardware RNG devices found:\n"); + printf("Testing each device for functionality...\n\n"); + + int working_devices = 0; + + for (int i = 0; i < num_devices; i++) { + printf("%d. %s at %s - ", i + 1, devices[i].friendly_name, devices[i].port_path); + + // Test the device + devices[i].is_working = test_hardware_rng_device(&devices[i]); + + if (devices[i].is_working) { + printf("āœ… WORKING\n"); + working_devices++; + } else { + printf("āŒ NOT WORKING\n"); + } + } + + if (working_devices == 0) { + printf("\nāŒ No working hardware RNG devices found.\n"); + printf("Please check device connections and permissions.\n"); + return 1; + } + + printf("\nSelect device to use (1-%d): ", num_devices); + + char input[10]; + if (!fgets(input, sizeof(input), stdin)) { + printf("Error reading input.\n"); + return 1; + } + + int choice = atoi(input); + if (choice < 1 || choice > num_devices) { + printf("Invalid selection.\n"); + return 1; + } + + int selected_index = choice - 1; + + if (!devices[selected_index].is_working) { + printf("āŒ Selected device is not working. Please choose a different device.\n"); + return 1; + } + + *selected_device = devices[selected_index]; + printf("Selected: %s at %s\n", selected_device->friendly_name, selected_device->port_path); + + return 0; // Success +} + +// Legacy function for backward compatibility - now uses the new multi-device detection +// Returns: 0=not found, 1=TrueRNGproV2, 2=TrueRNGpro, 3=TrueRNG +int find_truerng_port(char* port_path, size_t port_path_size, truerng_device_type_t* device_type) { + hardware_rng_device_t devices[10]; + int num_devices_found = 0; + + if (detect_all_hardware_rng_devices(devices, 10, &num_devices_found) != 0) { + return 0; // Error detecting devices + } + + if (num_devices_found == 0) { + return 0; // No devices found + } + + // For backward compatibility, return the first working device + for (int i = 0; i < num_devices_found; i++) { + if (test_hardware_rng_device(&devices[i])) { + // Found a working device + strncpy(port_path, devices[i].port_path, port_path_size - 1); + port_path[port_path_size - 1] = '\0'; + *device_type = devices[i].device_type; + return (devices[i].device_type == TRUERNG_PRO_V2) ? 1 : + (devices[i].device_type == TRUERNG_PRO) ? 2 : 3; + } + } + + return 0; // No working devices found +} + +// Setup serial port for TrueRNG communication (ported from TrueRNG reference) +int setup_truerng_serial_port(const char* port_path) { + int fd; + struct termios tty; + + fd = open(port_path, O_RDWR | O_NOCTTY); + if (fd < 0) { + return -1; + } + + // Get current port settings + if (tcgetattr(fd, &tty) != 0) { + close(fd); + return -1; + } + + // Set baud rate (TrueRNG devices use 9600) + cfsetospeed(&tty, B9600); + cfsetispeed(&tty, B9600); + + // 8N1 mode + tty.c_cflag &= ~PARENB; // No parity + tty.c_cflag &= ~CSTOPB; // 1 stop bit + tty.c_cflag &= ~CSIZE; // Clear size bits + tty.c_cflag |= CS8; // 8 data bits + tty.c_cflag &= ~CRTSCTS; // No hardware flow control + tty.c_cflag |= CREAD | CLOCAL; // Enable reading and ignore modem controls + + // Raw input mode + tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + tty.c_iflag &= ~(IXON | IXOFF | IXANY); + tty.c_oflag &= ~OPOST; + + // Set for blocking reads - wait for data indefinitely + tty.c_cc[VMIN] = 1; // Block until at least 1 character is received + tty.c_cc[VTIME] = 0; // No timeout + + // Apply settings + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + close(fd); + return -1; + } + + // Flush input buffer + tcflush(fd, TCIFLUSH); + + // Set DTR + int status; + ioctl(fd, TIOCMGET, &status); + status |= TIOCM_DTR; + ioctl(fd, TIOCMSET, &status); + + return fd; +} + +// Get friendly name for TrueRNG/SwiftRNG device type +const char* get_truerng_device_name(truerng_device_type_t device_type) { + switch (device_type) { + case TRUERNG_PRO_V2: return "TrueRNGproV2/SwiftRNGproV2"; + case TRUERNG_PRO: return "TrueRNGpro/SwiftRNGpro"; + case TRUERNG_ORIGINAL: return "TrueRNG/SwiftRNG"; + case SWIFT_RNG_PRO_V2: return "SwiftRNGproV2"; + case SWIFT_RNG_PRO: return "SwiftRNGpro"; + case SWIFT_RNG: return "SwiftRNG"; + default: return "Unknown"; + } +} + +// Test TrueRNG/SwiftRNG device speed and provide time estimates +int test_truerng_speed(void) { + char port_path[512]; + truerng_device_type_t device_type; + int serial_fd = -1; + + printf("\n"); + print_centered_header("TrueRNG/SwiftRNG Speed Test", 0); + + // Find TrueRNG device + if (!find_truerng_port(port_path, sizeof(port_path), &device_type)) { + printf("No TrueRNG/SwiftRNG device found.\n"); + printf("\nSupported devices:\n"); + printf(" - TrueRNG/SwiftRNG (PID: %s, VID: %s)\n", TRUERNG_VID, TRUERNG_PID); + printf(" - TrueRNGpro/SwiftRNGpro (PID: %s, VID: %s)\n", TRUERNGPRO_VID, TRUERNGPRO_PID); + printf(" - TrueRNGproV2/SwiftRNGproV2 (PID: %s, VID: %s)\n", TRUERNGPROV2_VID, TRUERNGPROV2_PID); + printf("\nPlease connect a TrueRNG or SwiftRNG device and try again.\n"); + return 1; + } + + printf("Found %s at %s\n", get_truerng_device_name(device_type), port_path); + printf("Running speed test...\n\n"); + + // Setup serial port + serial_fd = setup_truerng_serial_port(port_path); + if (serial_fd < 0) { + printf("Error: Cannot open TrueRNG device at %s\n", port_path); + printf("Check device permissions or run as root.\n"); + return 2; + } + + // Test with increasing sizes for comprehensive benchmarking + const size_t test_sizes[] = {1024, 1024*1024, 10*1024*1024}; // 1KB, 1MB, 10MB + const char* test_labels[] = {"1KB", "1MB", "10MB"}; + double speeds[3] = {0.0, 0.0, 0.0}; + + printf("Testing with different data sizes:\n"); + printf("%-8s %-12s %-12s %-12s\n", "Size", "Time", "Speed", "Status"); + printf("%-8s %-12s %-12s %-12s\n", "--------", "------------", "------------", "--------"); + + for (int i = 0; i < 3; i++) { + size_t test_bytes = test_sizes[i]; + unsigned char* test_buffer = malloc(test_bytes); + if (!test_buffer) { + printf("%-8s %-12s %-12s %-12s\n", test_labels[i], "N/A", "N/A", "MEM_ERR"); + continue; + } + + time_t test_start = time(NULL); + size_t test_collected = 0; + int test_failed = 0; + + while (test_collected < test_bytes && !test_failed) { + size_t bytes_needed = test_bytes - test_collected; + size_t read_size = (bytes_needed > 1024) ? 1024 : bytes_needed; + + ssize_t result = read(serial_fd, test_buffer + test_collected, read_size); + if (result <= 0) { + test_failed = 1; + break; + } + test_collected += result; + } + + if (test_failed) { + printf("%-8s %-12s %-12s %-12s\n", test_labels[i], "N/A", "N/A", "FAILED"); + free(test_buffer); + close(serial_fd); + return 3; + } + + double test_time = difftime(time(NULL), test_start); + if (test_time < 0.1) test_time = 0.1; // Minimum time to avoid division by zero + + double speed_kbps = (double)test_collected / test_time / 1024.0; + speeds[i] = speed_kbps; + + char time_str[16]; + if (test_time >= 1.0) { + snprintf(time_str, sizeof(time_str), "%.1fs", test_time); + } else { + snprintf(time_str, sizeof(time_str), "%.0fms", test_time * 1000.0); + } + + char speed_str[16]; + if (speed_kbps >= 1024.0) { + snprintf(speed_str, sizeof(speed_str), "%.1f MB/s", speed_kbps / 1024.0); + } else { + snprintf(speed_str, sizeof(speed_str), "%.0f KB/s", speed_kbps); + } + + printf("%-8s %-12s %-12s %-12s\n", test_labels[i], time_str, speed_str, "OK"); + + free(test_buffer); + } + + close(serial_fd); + + // Calculate average speed (weighted towards larger tests) + double avg_speed_kbps = (speeds[0] * 0.1 + speeds[1] * 0.3 + speeds[2] * 0.6); + + printf("\nšŸ“Š Performance Summary:\n"); + printf("Device: %s\n", get_truerng_device_name(device_type)); + printf("Average speed: %.0f KB/s (%.2f MB/s)\n", avg_speed_kbps, avg_speed_kbps / 1024.0); + + // Provide time estimates for common entropy collection amounts + printf("\nā±ļø Time Estimates for Entropy Collection:\n"); + + const size_t common_sizes[] = {1024, 2048, 4096, 1024*1024, 10*1024*1024, 100*1024*1024}; + const char* size_labels[] = {"1KB", "2KB", "4KB", "1MB", "10MB", "100MB"}; + + for (int i = 0; i < 6; i++) { + double est_time = (double)common_sizes[i] / (avg_speed_kbps * 1024.0); // Convert KB/s back to bytes/s + + char time_str[32]; + if (est_time < 1.0) { + snprintf(time_str, sizeof(time_str), "%.0fms", est_time * 1000.0); + } else if (est_time < 60.0) { + snprintf(time_str, sizeof(time_str), "%.1fs", est_time); + } else if (est_time < 3600.0) { + int mins = (int)(est_time / 60.0); + int secs = (int)(est_time) % 60; + snprintf(time_str, sizeof(time_str), "%dm %02ds", mins, secs); + } else { + int hours = (int)(est_time / 3600.0); + int mins = (int)(est_time / 60.0) % 60; + snprintf(time_str, sizeof(time_str), "%dh %02dm", hours, mins); + } + + printf(" %-6s → %s\n", size_labels[i], time_str); + } + + printf("\nāœ… Speed test completed successfully!\n"); + + // Pause before returning to menu + print_centered_header("Speed Test Complete", 1); + + return 0; +} + +// Collect entropy from TrueRNG device with equivalent quality to keyboard entropy +int collect_truerng_entropy(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int display_progress) { + char port_path[512]; + truerng_device_type_t device_type; + int serial_fd = -1; + + // Find TrueRNG device (legacy function for backward compatibility) + if (!find_truerng_port(port_path, sizeof(port_path), &device_type)) { + if (display_progress) { + printf("No TrueRNG/SwiftRNG device found.\n"); + printf("\nSupported devices:\n"); + printf(" - TrueRNG/SwiftRNG (PID: %s, VID: %s)\n", TRUERNG_VID, TRUERNG_PID); + printf(" - TrueRNGpro/SwiftRNGpro (PID: %s, VID: %s)\n", TRUERNGPRO_VID, TRUERNGPRO_PID); + printf(" - TrueRNGproV2/SwiftRNGproV2 (PID: %s, VID: %s)\n", TRUERNGPROV2_VID, TRUERNGPROV2_PID); + printf("\nPlease connect a TrueRNG or SwiftRNG device and try again.\n"); + } + return 1; // Device not found + } + + if (display_progress) { + printf("Found %s at %s\n", get_truerng_device_name(device_type), port_path); + printf("Collecting %zu bytes of entropy...\n", target_bytes); + } + + // Setup serial port + serial_fd = setup_truerng_serial_port(port_path); + if (serial_fd < 0) { + if (display_progress) { + printf("Error: Cannot open TrueRNG device at %s\n", port_path); + printf("Check device permissions or run as root.\n"); + } + return 2; // Serial port setup failed + } + + // Collect entropy data + size_t bytes_read = 0; + unsigned char buffer[1024]; // Read in 1KB chunks + time_t start_time = time(NULL); + + while (bytes_read < target_bytes) { + size_t chunk_size = sizeof(buffer); + if (target_bytes - bytes_read < chunk_size) { + chunk_size = target_bytes - bytes_read; + } + + size_t bytes_in_chunk = 0; + while (bytes_in_chunk < chunk_size) { + ssize_t result = read(serial_fd, buffer + bytes_in_chunk, chunk_size - bytes_in_chunk); + if (result < 0) { + if (display_progress) { + printf("Error: Failed to read from TrueRNG device\n"); + } + close(serial_fd); + return 3; // Read failed + } else if (result == 0) { + if (display_progress) { + printf("Error: No data received from TrueRNG device\n"); + } + close(serial_fd); + return 4; // No data + } + bytes_in_chunk += result; + } + + // Copy to entropy buffer + memcpy(entropy_buffer + bytes_read, buffer, chunk_size); + bytes_read += chunk_size; + + // Show progress for large collections + if (display_progress && bytes_read % (4 * 1024) == 0) { // Every 4KB + double percentage = (double)bytes_read / target_bytes * 100.0; + printf("Progress: %.1f%% (%zu/%zu bytes)\r", percentage, bytes_read, target_bytes); + fflush(stdout); + } + } + + close(serial_fd); + + if (display_progress) { + double collection_time = difftime(time(NULL), start_time); + printf("\nāœ“ TrueRNG entropy collection complete!\n"); + printf(" Collected: %zu bytes in %.1f seconds\n", bytes_read, collection_time); + printf(" Device: %s\n", get_truerng_device_name(device_type)); + if (collection_time > 0) { + printf(" Rate: %.1f KB/s\n", (double)bytes_read / collection_time / 1024.0); + } + } + + *collected_bytes = bytes_read; + return 0; // Success +} + +// Collect entropy from a specific TrueRNG/SwiftRNG device +// This function collects entropy from a user-selected device for pad enhancement +int collect_truerng_entropy_from_device(const hardware_rng_device_t* device, unsigned char* entropy_buffer, + size_t target_bytes, size_t* collected_bytes, int display_progress) { + int serial_fd = -1; + + if (display_progress) { + printf("Using %s at %s\n", device->friendly_name, device->port_path); + printf("Collecting %zu bytes of entropy...\n", target_bytes); + } + + // Setup serial port for the specific device + serial_fd = setup_truerng_serial_port(device->port_path); + if (serial_fd < 0) { + if (display_progress) { + printf("Error: Cannot open TrueRNG device at %s\n", device->port_path); + printf("Check device permissions or run as root.\n"); + } + return 2; // Serial port setup failed + } + + // Collect entropy data + size_t bytes_read = 0; + unsigned char buffer[1024]; // Read in 1KB chunks + time_t start_time = time(NULL); + + while (bytes_read < target_bytes) { + size_t chunk_size = sizeof(buffer); + if (target_bytes - bytes_read < chunk_size) { + chunk_size = target_bytes - bytes_read; + } + + size_t bytes_in_chunk = 0; + while (bytes_in_chunk < chunk_size) { + ssize_t result = read(serial_fd, buffer + bytes_in_chunk, chunk_size - bytes_in_chunk); + if (result < 0) { + if (display_progress) { + printf("Error: Failed to read from TrueRNG device\n"); + } + close(serial_fd); + return 3; // Read failed + } else if (result == 0) { + if (display_progress) { + printf("Error: No data received from TrueRNG device\n"); + } + close(serial_fd); + return 4; // No data + } + bytes_in_chunk += result; + } + + // Copy to entropy buffer + memcpy(entropy_buffer + bytes_read, buffer, chunk_size); + bytes_read += chunk_size; + + // Show progress for large collections + if (display_progress && bytes_read % (4 * 1024) == 0) { // Every 4KB + double percentage = (double)bytes_read / target_bytes * 100.0; + printf("Progress: %.1f%% (%zu/%zu bytes)\r", percentage, bytes_read, target_bytes); + fflush(stdout); + } + } + + close(serial_fd); + + if (display_progress) { + double collection_time = difftime(time(NULL), start_time); + printf("\nāœ“ TrueRNG entropy collection complete!\n"); + printf(" Collected: %zu bytes in %.1f seconds\n", bytes_read, collection_time); + printf(" Device: %s\n", device->friendly_name); + if (collection_time > 0) { + printf(" Rate: %.1f KB/s\n", (double)bytes_read / collection_time / 1024.0); + } + } + + *collected_bytes = bytes_read; + return 0; // Success +} + +// Helper function to format time in human-readable format +void format_time_remaining(double seconds, char* buffer, size_t buffer_size) { + if (seconds < 60) { + snprintf(buffer, buffer_size, "%.0fs", seconds); + } else if (seconds < 3600) { + int mins = (int)(seconds / 60); + int secs = (int)(seconds) % 60; + snprintf(buffer, buffer_size, "%dm %02ds", mins, secs); + } else { + int hours = (int)(seconds / 3600); + int mins = (int)(seconds / 60) % 60; + snprintf(buffer, buffer_size, "%dh %02dm", hours, mins); + } +} + +// Streaming TrueRNG entropy collection for full pad enhancement +// This function applies entropy directly to the pad in chunks to avoid memory issues +// entropy_mode: 0=direct XOR, 1=ChaCha20 distribution +int collect_truerng_entropy_streaming(const char* pad_chksum, size_t total_bytes, int display_progress, int entropy_mode) { + char port_path[512]; + truerng_device_type_t device_type; + int serial_fd = -1; + + // Find TrueRNG device + if (!find_truerng_port(port_path, sizeof(port_path), &device_type)) { + if (display_progress) { + printf("No TrueRNG/SwiftRNG device found.\n"); + printf("\nSupported devices:\n"); + printf(" - TrueRNG/SwiftRNG (PID: %s, VID: %s)\n", TRUERNG_VID, TRUERNG_PID); + printf(" - TrueRNGpro/SwiftRNGpro (PID: %s, VID: %s)\n", TRUERNGPRO_VID, TRUERNGPRO_PID); + printf(" - TrueRNGproV2/SwiftRNGproV2 (PID: %s, VID: %s)\n", TRUERNGPROV2_VID, TRUERNGPROV2_PID); + printf("\nPlease connect a TrueRNG or SwiftRNG device and try again.\n"); + } + return 1; // Device not found + } + + if (display_progress) { + printf("Found %s at %s\n", get_truerng_device_name(device_type), port_path); + printf("Streaming %zu bytes of entropy directly to pad...\n", total_bytes); + } + + // Setup serial port + serial_fd = setup_truerng_serial_port(port_path); + if (serial_fd < 0) { + if (display_progress) { + printf("Error: Cannot open TrueRNG device at %s\n", port_path); + printf("Check device permissions or run as root.\n"); + } + return 2; // Serial port setup failed + } + + // For large pads (>10MB), do a 1MB test to estimate completion time + double estimated_rate = 0.0; + int user_confirmed = 1; // Default to confirmed for small pads + + if (total_bytes > 10 * 1024 * 1024 && display_progress) { + printf("\nLarge pad detected (%.1f MB). Running 1MB speed test...\n", + (double)total_bytes / (1024.0 * 1024.0)); + + // Test with 1MB sample + size_t test_bytes = 1024 * 1024; // 1MB + unsigned char* test_buffer = malloc(test_bytes); + if (!test_buffer) { + printf("Error: Cannot allocate test buffer\n"); + close(serial_fd); + return 3; + } + + time_t test_start = time(NULL); + size_t test_collected = 0; + + while (test_collected < test_bytes) { + size_t bytes_needed = test_bytes - test_collected; + size_t read_size = (bytes_needed > 1024) ? 1024 : bytes_needed; + + ssize_t result = read(serial_fd, test_buffer + test_collected, read_size); + if (result <= 0) { + printf("Error: TrueRNG test failed\n"); + free(test_buffer); + close(serial_fd); + return 4; + } + test_collected += result; + + // Show test progress + double test_progress = (double)test_collected / test_bytes * 100.0; + printf("\rSpeed test: %.1f%% complete", test_progress); + fflush(stdout); + } + + double test_time = difftime(time(NULL), test_start); + estimated_rate = (double)test_bytes / test_time; // bytes per second + + // Calculate estimated total time + double estimated_total_time = (double)total_bytes / estimated_rate; + + char time_str[64]; + format_time_remaining(estimated_total_time, time_str, sizeof(time_str)); + + printf("\nSpeed test complete: %.1f KB/s\n", estimated_rate / 1024.0); + printf("Estimated completion time: %s\n", time_str); + printf("\nProceed with full pad enhancement? (y/N): "); + fflush(stdout); + + char response[10]; + if (fgets(response, sizeof(response), stdin) == NULL || + (response[0] != 'y' && response[0] != 'Y')) { + printf("Operation cancelled by user.\n"); + free(test_buffer); + close(serial_fd); + return 5; // User cancelled + } + + user_confirmed = 1; + free(test_buffer); + + // Reset serial port for main operation + close(serial_fd); + serial_fd = setup_truerng_serial_port(port_path); + if (serial_fd < 0) { + printf("Error: Cannot reopen TrueRNG device\n"); + return 6; + } + } + + if (!user_confirmed) { + close(serial_fd); + return 5; // User cancelled + } + + // Get pad paths + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Open pad file for read/write with temporary permissions + if (chmod(pad_path, S_IRUSR | S_IWUSR) != 0) { + if (display_progress) { + printf("Error: Cannot change pad file permissions\n"); + } + close(serial_fd); + return 3; + } + + FILE* pad_file = fopen(pad_path, "r+b"); + if (!pad_file) { + if (display_progress) { + printf("Error: Cannot open pad file for modification: %s\n", pad_path); + } + chmod(pad_path, S_IRUSR); // Restore read-only + close(serial_fd); + return 4; + } + + // Process pad in chunks + const size_t chunk_size = 1024 * 1024; // 1MB chunks + unsigned char* entropy_chunk = malloc(chunk_size); + unsigned char* pad_chunk = malloc(chunk_size); + unsigned char* mixed_chunk = malloc(chunk_size); + + if (!entropy_chunk || !pad_chunk || !mixed_chunk) { + if (display_progress) { + printf("Error: Cannot allocate chunk buffers\n"); + } + free(entropy_chunk); + free(pad_chunk); + free(mixed_chunk); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + close(serial_fd); + return 5; + } + + size_t bytes_processed = 0; + time_t start_time = time(NULL); + + while (bytes_processed < total_bytes) { + size_t current_chunk_size = chunk_size; + if (total_bytes - bytes_processed < chunk_size) { + current_chunk_size = total_bytes - bytes_processed; + } + + // Collect entropy from TrueRNG for this chunk + size_t entropy_bytes_in_chunk = 0; + while (entropy_bytes_in_chunk < current_chunk_size) { + size_t bytes_needed = current_chunk_size - entropy_bytes_in_chunk; + size_t read_size = (bytes_needed > 1024) ? 1024 : bytes_needed; + + ssize_t result = read(serial_fd, entropy_chunk + entropy_bytes_in_chunk, read_size); + if (result < 0) { + if (display_progress) { + printf("Error: Failed to read from TrueRNG device\n"); + } + free(entropy_chunk); + free(pad_chunk); + free(mixed_chunk); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + close(serial_fd); + return 6; + } else if (result == 0) { + if (display_progress) { + printf("Error: No data received from TrueRNG device\n"); + } + free(entropy_chunk); + free(pad_chunk); + free(mixed_chunk); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + close(serial_fd); + return 7; + } + entropy_bytes_in_chunk += result; + } + + // Read current pad data for this chunk + if (fseek(pad_file, bytes_processed, SEEK_SET) != 0) { + if (display_progress) { + printf("Error: Cannot seek to offset %zu in pad file\n", bytes_processed); + } + free(entropy_chunk); + free(pad_chunk); + free(mixed_chunk); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + close(serial_fd); + return 8; + } + + if (fread(pad_chunk, 1, current_chunk_size, pad_file) != current_chunk_size) { + if (display_progress) { + printf("Error: Cannot read pad data at offset %zu\n", bytes_processed); + } + free(entropy_chunk); + free(pad_chunk); + free(mixed_chunk); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + close(serial_fd); + return 9; + } + + // Apply entropy based on selected mode + if (entropy_mode == 0) { + // Direct XOR mode - use entropy directly + for (size_t i = 0; i < current_chunk_size; i++) { + mixed_chunk[i] = pad_chunk[i] ^ entropy_chunk[i % current_chunk_size]; + } + } else { + // ChaCha20 mode - derive key and nonce from entropy chunk + unsigned char key[32], nonce[12]; + if (derive_chacha20_params(entropy_chunk, current_chunk_size, key, nonce) != 0) { + if (display_progress) { + printf("Error: Failed to derive Chacha20 parameters from entropy\n"); + } + free(entropy_chunk); + free(pad_chunk); + free(mixed_chunk); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + close(serial_fd); + return 10; + } + + // Generate keystream and XOR with pad data + uint32_t counter = bytes_processed / 64; // Chacha20 block counter + if (chacha20_encrypt(key, counter, nonce, pad_chunk, mixed_chunk, current_chunk_size) != 0) { + if (display_progress) { + printf("Error: Chacha20 keystream generation failed\n"); + } + free(entropy_chunk); + free(pad_chunk); + free(mixed_chunk); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + close(serial_fd); + return 11; + } + + // XOR existing pad with keystream (adds entropy) + for (size_t i = 0; i < current_chunk_size; i++) { + mixed_chunk[i] = pad_chunk[i] ^ mixed_chunk[i]; + } + } + + // Write modified data back to pad + if (fseek(pad_file, bytes_processed, SEEK_SET) != 0) { + if (display_progress) { + printf("Error: Cannot seek to write offset %zu\n", bytes_processed); + } + free(entropy_chunk); + free(pad_chunk); + free(mixed_chunk); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + close(serial_fd); + return 12; + } + + if (fwrite(mixed_chunk, 1, current_chunk_size, pad_file) != current_chunk_size) { + if (display_progress) { + printf("Error: Cannot write modified pad data\n"); + } + free(entropy_chunk); + free(pad_chunk); + free(mixed_chunk); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + close(serial_fd); + return 13; + } + + bytes_processed += current_chunk_size; + + // Show progress and entropy samples for visual verification + if (display_progress) { + // Update progress more frequently for better user experience - always show cumulative amount + if (bytes_processed % (4 * 1024 * 1024) == 0 || bytes_processed == total_bytes) { // Every 4MB or at completion + double percentage = (double)bytes_processed / total_bytes * 100.0; + double elapsed = difftime(time(NULL), start_time); + double rate = 0.0; + if (elapsed > 0) { + rate = (double)bytes_processed / elapsed / (1024.0 * 1024.0); + } + + // Calculate estimated time remaining + char eta_str[64] = ""; + if (rate > 0.0 && bytes_processed < total_bytes) { + double remaining_bytes = (double)(total_bytes - bytes_processed); + double eta_seconds = remaining_bytes / (rate * 1024.0 * 1024.0); // Convert rate back to bytes/sec + format_time_remaining(eta_seconds, eta_str, sizeof(eta_str)); + } + + // Clear previous line and show progress bar with cumulative TrueRNG data generated + printf("\r\033[K"); // Clear line + printf("["); + int bar_width = 30; + int filled = (int)(percentage / 100.0 * bar_width); + for (int i = 0; i < filled; i++) printf("ā–ˆ"); + for (int i = filled; i < bar_width; i++) printf("ā–‘"); + + if (strlen(eta_str) > 0) { + printf("] %.1f%% - TrueRNG: %zu MB / %zu MB (%.1f MB/s) ETA: %s", + percentage, bytes_processed / (1024*1024), total_bytes / (1024*1024), rate, eta_str); + } else { + printf("] %.1f%% - TrueRNG: %zu MB / %zu MB (%.1f MB/s)", + percentage, bytes_processed / (1024*1024), total_bytes / (1024*1024), rate); + } + fflush(stdout); + } + + // Show entropy samples every 64MB for visual verification of randomness + if (bytes_processed % (64 * 1024 * 1024) == 0 && bytes_processed > 0) { + printf("\nšŸ”¬ TrueRNG entropy sample: "); + // Display first 16 bytes of current entropy chunk as hex + size_t sample_size = (current_chunk_size < 16) ? current_chunk_size : 16; + for (size_t i = 0; i < sample_size; i++) { + printf("%02x", entropy_chunk[i]); + if (i == 7) printf(" "); // Space in middle for readability + } + printf("\n"); + } + } + } + + // Cleanup + free(entropy_chunk); + free(pad_chunk); + free(mixed_chunk); + fclose(pad_file); + close(serial_fd); + + // Restore read-only permissions + if (chmod(pad_path, S_IRUSR) != 0) { + if (display_progress) { + printf("Warning: Cannot restore pad file to read-only\n"); + } + } + + if (display_progress) { + double collection_time = difftime(time(NULL), start_time); + printf("\nāœ“ TrueRNG streaming entropy collection complete!\n"); + printf(" Enhanced: %zu bytes in %.1f seconds\n", bytes_processed, collection_time); + printf(" Device: %s\n", get_truerng_device_name(device_type)); + if (collection_time > 0) { + printf(" Rate: %.1f MB/s\n", (double)bytes_processed / collection_time / (1024.0*1024.0)); + } + printf("āœ“ Pad integrity maintained\n"); + printf("āœ“ Entropy distributed across entire pad\n"); + printf("āœ“ Pad restored to read-only mode\n"); + + // Pause before returning to menu to let user see the success message + print_centered_header("TrueRNG Enhancement Complete", 1); + } + + return 0; // Success +} + +// Collect manual entropy from any printable character input +int collect_dice_entropy(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int display_progress) { + if (display_progress) { + print_centered_header("Manual Entropy Collection", 0); + printf("Enter any text, numbers, or symbols for entropy.\n"); + printf("Target: %zu bytes (%zu characters needed)\n", target_bytes, target_bytes); + printf("Press Enter after each line, or 'done' when finished.\n\n"); + } + + size_t bytes_written = 0; + + char input[256]; + + while (bytes_written < target_bytes) { + if (display_progress) { + double percentage = (double)bytes_written / target_bytes * 100.0; + printf("Progress: %.1f%% (%zu/%zu bytes) - Enter text: ", + percentage, bytes_written, target_bytes); + fflush(stdout); + } + + if (!fgets(input, sizeof(input), stdin)) { + if (display_progress) { + printf("Error: Failed to read input\n"); + } + return 1; + } + + // Remove newline + input[strcspn(input, "\n")] = 0; + + // Check for done command + if (strcmp(input, "done") == 0 && bytes_written >= target_bytes / 2) { + break; // Allow early exit if we have at least half the target + } + + // Process each printable character as 8 bits of entropy + for (size_t i = 0; input[i] && bytes_written < target_bytes; i++) { + char c = input[i]; + if (c >= 32 && c <= 126) { // Printable ASCII characters + entropy_buffer[bytes_written++] = (unsigned char)c; + } + } + } + + if (display_progress) { + printf("\nāœ“ Manual entropy collection complete!\n"); + printf(" Collected: %zu bytes from text input\n", bytes_written); + printf(" Entropy quality: 8 bits per character\n"); + } + + *collected_bytes = bytes_written; + return 0; // Success +} + +// Collect entropy from binary file +int collect_file_entropy(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int display_progress) { + if (display_progress) { + print_centered_header("File Entropy Collection", 0); + printf("Load entropy from binary file (.bin format)\n"); + printf("Target: %zu bytes\n", target_bytes); + } + + printf("Enter path to binary entropy file: "); + fflush(stdout); + + char file_path[512]; + if (!fgets(file_path, sizeof(file_path), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + // Remove newline + file_path[strcspn(file_path, "\n")] = 0; + + // Check if file exists and get size + struct stat file_stat; + if (stat(file_path, &file_stat) != 0) { + printf("Error: File '%s' not found\n", file_path); + return 1; + } + + if (!S_ISREG(file_stat.st_mode)) { + printf("Error: '%s' is not a regular file\n", file_path); + return 1; + } + + size_t file_size = file_stat.st_size; + if (file_size == 0) { + printf("Error: File is empty\n"); + return 1; + } + + if (file_size < target_bytes) { + printf("Warning: File size (%zu bytes) is smaller than target (%zu bytes)\n", + file_size, target_bytes); + printf("Will read available data and pad with zeros if necessary.\n"); + } + + // Open file for reading + FILE* entropy_file = fopen(file_path, "rb"); + if (!entropy_file) { + printf("Error: Cannot open file '%s' for reading\n", file_path); + return 1; + } + + if (display_progress) { + printf("Reading entropy from file...\n"); + } + + // Read entropy data + size_t bytes_to_read = (file_size < target_bytes) ? file_size : target_bytes; + size_t bytes_read = fread(entropy_buffer, 1, bytes_to_read, entropy_file); + + if (bytes_read != bytes_to_read) { + printf("Error: Failed to read %zu bytes from file (read %zu)\n", + bytes_to_read, bytes_read); + fclose(entropy_file); + return 1; + } + + fclose(entropy_file); + + // Pad with zeros if file was smaller than target + if (bytes_read < target_bytes) { + memset(entropy_buffer + bytes_read, 0, target_bytes - bytes_read); + *collected_bytes = target_bytes; // We padded to target size + } else { + *collected_bytes = bytes_read; + } + + if (display_progress) { + printf("āœ“ File entropy collection complete!\n"); + printf(" File: %s\n", file_path); + printf(" Read: %zu bytes\n", bytes_read); + printf(" Total: %zu bytes (padded to target if necessary)\n", *collected_bytes); + } + + return 0; // Success +} + +// Collect entropy by source type with unified interface +int collect_entropy_by_source(entropy_source_t source, unsigned char* entropy_buffer, + size_t target_bytes, size_t* collected_bytes, int display_progress) { + switch (source) { + case ENTROPY_SOURCE_KEYBOARD: + return collect_entropy_with_feedback(entropy_buffer, target_bytes, collected_bytes, 1); + + case ENTROPY_SOURCE_TRUERNG: + return collect_truerng_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress); + + case ENTROPY_SOURCE_DICE: + return collect_dice_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress); + + case ENTROPY_SOURCE_FILE: + return collect_file_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress); + + default: + if (display_progress) { + printf("Error: Unknown entropy source\n"); + } + return 1; + } +} + +double get_precise_time(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec + ts.tv_nsec / 1000000000.0; +} + +void draw_progress_bar(double percentage, int width) { + int filled = (int)(percentage / 100.0 * width); + if (filled > width) filled = width; + + printf("["); + for (int i = 0; i < filled; i++) { + printf("ā–ˆ"); + } + for (int i = filled; i < width; i++) { + printf("ā–‘"); + } + printf("]"); +} + +void draw_quality_bar(double quality, int width, const char* label) { + int filled = (int)(quality / 100.0 * width); + if (filled > width) filled = width; + + // Color coding based on quality + const char* color; + if (quality >= 80) color = "\033[32m"; // Green + else if (quality >= 60) color = "\033[33m"; // Yellow + else color = "\033[31m"; // Red + + printf("%s", color); + draw_progress_bar(quality, width); + printf("\033[0m %-10s", label); // Reset color +} + +double calculate_timing_quality(const entropy_collection_state_t* state) { + // Analyze timing variance between keypresses + if (state->collected_bytes < 32) return 0.0; // Need minimum data + + // Simplified timing quality based on collection rate and variation + double elapsed = get_precise_time() - state->collection_start_time; + if (elapsed < 0.1) return 0.0; + + double rate = state->collected_bytes / elapsed; + + // Optimal rate is around 50-200 bytes/second (moderate typing with good timing variance) + if (rate >= 50 && rate <= 200) return 90.0; + if (rate >= 20 && rate <= 500) return 70.0; + if (rate >= 10 && rate <= 1000) return 50.0; + return 30.0; +} + +double calculate_variety_quality(const entropy_collection_state_t* state) { + // Analyze key variety and distribution + if (state->collected_bytes < 16) return 0.0; + + // Calculate entropy from key histogram + double entropy = 0.0; + size_t total_keys = 0; + + // Count total keypresses + for (int i = 0; i < 256; i++) { + total_keys += state->key_histogram[i]; + } + + if (total_keys == 0) return 0.0; + + // Calculate Shannon entropy + for (int i = 0; i < 256; i++) { + if (state->key_histogram[i] > 0) { + double p = (double)state->key_histogram[i] / total_keys; + entropy -= p * log2(p); + } + } + + // Convert entropy to quality score (0-100) + double max_entropy = log2(256); // Perfect entropy for 8-bit keyspace + double normalized_entropy = entropy / max_entropy; + + // Scale based on unique keys as well + double unique_key_factor = (double)state->unique_keys / 50.0; // 50+ unique keys is excellent + if (unique_key_factor > 1.0) unique_key_factor = 1.0; + + return (normalized_entropy * 70.0 + unique_key_factor * 30.0); +} + +unsigned char calculate_overall_quality(const entropy_collection_state_t* state) { + double timing = calculate_timing_quality(state); + double variety = calculate_variety_quality(state); + + // Simple collection progress bonus + double progress_bonus = (double)state->collected_bytes / state->target_bytes * 20.0; + if (progress_bonus > 20.0) progress_bonus = 20.0; + + // Weighted average + double overall = (timing * 0.4 + variety * 0.4 + progress_bonus); + if (overall > 100.0) overall = 100.0; + + return (unsigned char)overall; +} + +void display_entropy_progress(const entropy_collection_state_t* state) { + // Calculate percentages + double progress = (double)state->collected_bytes / state->target_bytes * 100.0; + if (progress > 100.0) progress = 100.0; + + double quality = state->quality_score; + double timing_quality = calculate_timing_quality(state); + double variety_quality = calculate_variety_quality(state); + + // Clear previous output and redraw + printf("\033[2K\r"); // Clear line + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + + // Header + printf("Adding Entropy to Pad - Target: %zu bytes\n\n", state->target_bytes); + + // Main progress bar + printf("Progress: "); + draw_progress_bar(progress, 50); + printf(" %.1f%% (%zu/%zu bytes)\n", progress, state->collected_bytes, state->target_bytes); + + // Quality indicators + printf("Quality: "); + draw_quality_bar(quality, 50, "OVERALL"); + printf("\n"); + + printf("Timing: "); + draw_quality_bar(timing_quality, 50, "VARIED"); + printf("\n"); + + printf("Keys: "); + draw_quality_bar(variety_quality, 50, "DIVERSE"); + printf("\n"); + + // Instructions + if (state->collected_bytes >= 1024 && state->auto_complete_enabled) { + printf("\nPress ESC to finish (minimum reached) or continue typing..."); + } else if (state->collected_bytes < 1024) { + printf("\nType random keys... (%zu more bytes needed)", 1024 - state->collected_bytes); + } else { + printf("\nType random keys or press ESC when satisfied..."); + } + + fflush(stdout); +} + +// Chacha20 key derivation from collected entropy +int derive_chacha20_params(const unsigned char* entropy_data, size_t entropy_size, + unsigned char key[32], unsigned char nonce[12]) { + if (!entropy_data || entropy_size < 512 || !key || !nonce) { + return 1; // Error: insufficient entropy or null pointers + } + + // Phase 1: Generate base key from entropy using enhanced XOR checksum method + unsigned char enhanced_checksum[44]; // 32 key + 12 nonce + memset(enhanced_checksum, 0, 44); + + // Mix entropy data similar to calculate_checksum but for 44 bytes + for (size_t i = 0; i < entropy_size; i++) { + unsigned char bucket = i % 44; + enhanced_checksum[bucket] ^= entropy_data[i] ^ + ((i >> 8) & 0xFF) ^ + ((i >> 16) & 0xFF) ^ + ((i >> 24) & 0xFF); + } + + // Phase 2: Add system entropy for additional randomness + unsigned char system_entropy[32]; + FILE* urandom = fopen("/dev/urandom", "rb"); + if (!urandom) { + return 2; // Error: cannot access system entropy + } + + if (fread(system_entropy, 1, 32, urandom) != 32) { + fclose(urandom); + return 2; // Error: insufficient system entropy + } + fclose(urandom); + + // Mix system entropy into derived key + for (int i = 0; i < 32; i++) { + enhanced_checksum[i] ^= system_entropy[i]; + } + + // Extract key and nonce + memcpy(key, enhanced_checksum, 32); + memcpy(nonce, enhanced_checksum + 32, 12); + + return 0; // Success +} + +// Check if a pad is unused (0% usage) +int is_pad_unused(const char* pad_chksum) { + uint64_t used_bytes; + if (read_state_offset(pad_chksum, &used_bytes) != 0) { + return 0; // Error reading state, assume used + } + return (used_bytes <= 32); // Only reserved bytes used (32 bytes for checksum encryption) +} + +// Safely rename pad files (pad and state) from old to new checksum +int rename_pad_files_safely(const char* old_chksum, const char* new_chksum) { + char old_pad_path[1024], new_pad_path[1024]; + char old_state_path[1024], new_state_path[1024]; + + // Construct file paths + snprintf(old_pad_path, sizeof(old_pad_path), "%s/%s.pad", current_pads_dir, old_chksum); + snprintf(new_pad_path, sizeof(new_pad_path), "%s/%s.pad", current_pads_dir, new_chksum); + snprintf(old_state_path, sizeof(old_state_path), "%s/%s.state", current_pads_dir, old_chksum); + snprintf(new_state_path, sizeof(new_state_path), "%s/%s.state", current_pads_dir, new_chksum); + + // Check if new files would conflict with existing files + if (access(new_pad_path, F_OK) == 0) { + printf("Error: New pad file already exists: %s\n", new_pad_path); + return 1; // Conflict + } + + // Rename pad file + if (rename(old_pad_path, new_pad_path) != 0) { + printf("Error: Failed to rename pad file from %s to %s\n", old_pad_path, new_pad_path); + return 2; // Pad rename failed + } + + // Rename state file (if it exists) + if (access(old_state_path, F_OK) == 0) { + if (rename(old_state_path, new_state_path) != 0) { + printf("Warning: Failed to rename state file, but pad file was renamed successfully\n"); + // Try to rollback pad file rename + rename(new_pad_path, old_pad_path); + return 3; // State rename failed + } + } + + return 0; // Success +} + +// Update pad checksum after entropy addition +int update_pad_checksum_after_entropy(const char* old_chksum, char* new_chksum) { + char pad_path[1024]; + snprintf(pad_path, sizeof(pad_path), "%s/%s.pad", current_pads_dir, old_chksum); + + // Calculate new checksum of the modified pad + if (calculate_checksum(pad_path, new_chksum) != 0) { + printf("Error: Cannot calculate new pad checksum\n"); + return 1; + } + + // Check if checksum actually changed + if (strcmp(old_chksum, new_chksum) == 0) { + printf("Warning: Pad checksum unchanged after entropy addition\n"); + return 2; // Checksum didn't change (unusual but not fatal) + } + + // Rename pad files to use new checksum + if (rename_pad_files_safely(old_chksum, new_chksum) != 0) { + return 3; // Rename failed + } + + // Update default pad preference if this was the default pad + char* current_default = get_default_pad_path(); + if (current_default) { + // Check if the old pad was the default + if (strstr(current_default, old_chksum)) { + // Update to new checksum + char new_default_path[1024]; + snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, new_chksum); + + if (set_default_pad_path(new_default_path) != 0) { + printf("Warning: Failed to update default pad preference\n"); + } else { + printf("Updated default pad to new checksum: %.16s...\n", new_chksum); + } + } + free(current_default); + } + + return 0; // Success +} + +// Enhanced entropy collection with visual feedback +int collect_entropy_with_feedback(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int allow_early_exit) { + struct termios original_termios; + entropy_collection_state_t state = {0}; + + // Initialize state + state.target_bytes = target_bytes; + state.auto_complete_enabled = allow_early_exit; + state.collection_start_time = get_precise_time(); + + // Setup raw terminal + if (setup_raw_terminal(&original_termios) != 0) { + printf("Error: Cannot setup terminal for entropy collection\n"); + return 1; + } + + // Clear screen area for display + printf("\n\n\n\n\n\n"); + + unsigned char entropy_block[16]; + struct timespec timestamp; + uint32_t sequence_counter = 0; + char key; + unsigned char seen_keys[256] = {0}; + + *collected_bytes = 0; + + while (state.collected_bytes < target_bytes) { + // Update display + state.quality_score = calculate_overall_quality(&state); + display_entropy_progress(&state); + + // Non-blocking read + if (read(STDIN_FILENO, &key, 1) == 1) { + // Handle ESC key for early exit + if (key == 27 && allow_early_exit && state.collected_bytes >= 1024) { + break; // Early exit allowed + } + + // Record keypress timing + double current_time = get_precise_time(); + state.last_keypress_time = current_time; + + // Update key histogram + state.key_histogram[(unsigned char)key]++; + + // Get high precision timestamp + clock_gettime(CLOCK_MONOTONIC, ×tamp); + + // Create enhanced entropy block: [key][timestamp][sequence][quality_bits] + entropy_block[0] = key; + memcpy(&entropy_block[1], ×tamp.tv_sec, 8); + memcpy(&entropy_block[9], ×tamp.tv_nsec, 4); + memcpy(&entropy_block[13], &sequence_counter, 2); + entropy_block[15] = (unsigned char)(current_time * 1000) & 0xFF; // Sub-millisecond timing + + // Add to entropy buffer + if (state.collected_bytes + 16 <= MAX_ENTROPY_BUFFER) { + memcpy(entropy_buffer + state.collected_bytes, entropy_block, 16); + state.collected_bytes += 16; + } + + sequence_counter++; + + // Track unique keys + if (!seen_keys[(unsigned char)key]) { + seen_keys[(unsigned char)key] = 1; + state.unique_keys++; + } + } else { + // No key available, just sleep and wait for keystrokes + usleep(10000); // 10ms delay - wait for keystrokes, don't add timing entropy + } + + // Auto-complete at target if enabled + if (state.collected_bytes >= target_bytes) { + break; + } + } + + // Final display update + state.quality_score = calculate_overall_quality(&state); + display_entropy_progress(&state); + + // Summary + double collection_time = get_precise_time() - state.collection_start_time; + printf("\n\nāœ“ Entropy collection complete!\n"); + printf(" Collected: %zu bytes in %.1f seconds\n", state.collected_bytes, collection_time); + printf(" Quality: %d%% (Excellent: 80%%+, Good: 60%%+)\n", state.quality_score); + printf(" Unique keys: %zu\n", state.unique_keys); + + // Restore terminal + restore_terminal(&original_termios); + + *collected_bytes = state.collected_bytes; + return 0; +} + +// Keyboard entropy functions +int setup_raw_terminal(struct termios* original_termios) { + struct termios new_termios; + + if (tcgetattr(STDIN_FILENO, original_termios) != 0) { + return 1; + } + + new_termios = *original_termios; + new_termios.c_lflag &= ~(ICANON | ECHO); + new_termios.c_cc[VMIN] = 0; + new_termios.c_cc[VTIME] = 0; + + if (tcsetattr(STDIN_FILENO, TCSANOW, &new_termios) != 0) { + return 1; + } + + // Set stdin to non-blocking + int flags = fcntl(STDIN_FILENO, F_GETFL); + if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) == -1) { + tcsetattr(STDIN_FILENO, TCSANOW, original_termios); + return 1; + } + + return 0; +} + +void restore_terminal(struct termios* original_termios) { + tcsetattr(STDIN_FILENO, TCSANOW, original_termios); + + // Reset stdin to blocking + int flags = fcntl(STDIN_FILENO, F_GETFL); + fcntl(STDIN_FILENO, F_SETFL, flags & ~O_NONBLOCK); +} + + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UNIVERSAL CORE FUNCTIONS FOR CODE CONSOLIDATION +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +// Universal XOR operation - handles both encryption and decryption +// Since XOR is symmetric, this single function replaces all 6 duplicate XOR loops +// MOVED TO src/crypto.c - commented out here +/* +int universal_xor_operation(const unsigned char* data, size_t data_len, + const unsigned char* pad_data, unsigned char* result) { + if (!data || !pad_data || !result) { + return 1; // Error: null pointer + } + + for (size_t i = 0; i < data_len; i++) { + result[i] = data[i] ^ pad_data[i]; + } + + return 0; // Success +} +*/ + +// Universal ASCII message parser - consolidates 4 duplicate parsing implementations +// Extracts checksum, offset, and base64 data from ASCII armored messages +int parse_ascii_message(const char* message, char* chksum, uint64_t* offset, char* base64_data) { + if (!message || !chksum || !offset || !base64_data) { + return 1; // Error: null pointer + } + + char *message_copy = strdup(message); + if (!message_copy) { + return 1; // Memory allocation failed + } + + char *line_ptr = strtok(message_copy, "\n"); + int found_begin = 0; + int in_data_section = 0; + int found_chksum = 0, found_offset = 0; + + // Initialize output + chksum[0] = '\0'; + *offset = 0; + base64_data[0] = '\0'; + + while (line_ptr != NULL) { + if (strcmp(line_ptr, "-----BEGIN OTP MESSAGE-----") == 0) { + found_begin = 1; + } + else if (strcmp(line_ptr, "-----END OTP MESSAGE-----") == 0) { + break; + } + else if (found_begin) { + if (strncmp(line_ptr, "Pad-ChkSum: ", 12) == 0) { + strncpy(chksum, line_ptr + 12, 64); + chksum[64] = '\0'; + found_chksum = 1; + } + else if (strncmp(line_ptr, "Pad-Offset: ", 12) == 0) { + *offset = strtoull(line_ptr + 12, NULL, 10); + found_offset = 1; + } + else if (strlen(line_ptr) == 0) { + in_data_section = 1; + } + else if (in_data_section) { + strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1); + } + else if (strncmp(line_ptr, "Version:", 8) != 0 && strncmp(line_ptr, "Pad-", 4) != 0) { + // This might be base64 data without a blank line separator + strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1); + } + } + line_ptr = strtok(NULL, "\n"); + } + + free(message_copy); + + if (!found_begin || !found_chksum || !found_offset) { + return 2; // Error: incomplete message format + } + + return 0; // Success +} + +// Universal pad data loader - consolidates pad loading and validation logic +// Loads pad data at specified offset and validates pad availability +int load_pad_data(const char* pad_chksum, uint64_t offset, size_t length, unsigned char** pad_data) { + if (!pad_chksum || !pad_data) { + return 1; // Error: null pointer + } + + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Check if pad file exists + if (access(pad_path, R_OK) != 0) { + return 2; // Error: pad file not found + } + + // Check pad file size + struct stat pad_stat; + if (stat(pad_path, &pad_stat) != 0) { + return 3; // Error: cannot get pad file size + } + + if (offset + length > (uint64_t)pad_stat.st_size) { + return 4; // Error: not enough pad space + } + + // Allocate memory for pad data + *pad_data = malloc(length); + if (!*pad_data) { + return 5; // Error: memory allocation failed + } + + // Open and read pad file + FILE* pad_file = fopen(pad_path, "rb"); + if (!pad_file) { + free(*pad_data); + *pad_data = NULL; + return 6; // Error: cannot open pad file + } + + if (fseek(pad_file, offset, SEEK_SET) != 0) { + fclose(pad_file); + free(*pad_data); + *pad_data = NULL; + return 7; // Error: cannot seek to offset + } + + if (fread(*pad_data, 1, length, pad_file) != length) { + fclose(pad_file); + free(*pad_data); + *pad_data = NULL; + return 8; // Error: cannot read pad data + } + + fclose(pad_file); + return 0; // Success +} + +// Universal ASCII armor generator - consolidates duplicate ASCII armor generation +// Creates ASCII armored output format used by both text and file encryption +int generate_ascii_armor(const char* chksum, uint64_t offset, const unsigned char* encrypted_data, + size_t data_length, char** ascii_output) { + if (!chksum || !encrypted_data || !ascii_output) { + return 1; // Error: null pointer + } + + // Encode data as base64 + char* base64_data = custom_base64_encode(encrypted_data, data_length); + if (!base64_data) { + return 2; // Error: base64 encoding failed + } + + // Calculate required buffer size + size_t base64_len = strlen(base64_data); + size_t header_size = 200; // Approximate size for headers + size_t total_size = header_size + base64_len + (base64_len / 64) + 100; // +newlines +footer + + *ascii_output = malloc(total_size); + if (!*ascii_output) { + free(base64_data); + return 3; // Error: memory allocation failed + } + + // Build ASCII armor + strcpy(*ascii_output, "-----BEGIN OTP MESSAGE-----\n"); + + char temp_line[256]; + snprintf(temp_line, sizeof(temp_line), "Version: v0.3.16\n"); + strcat(*ascii_output, temp_line); + + snprintf(temp_line, sizeof(temp_line), "Pad-ChkSum: %s\n", chksum); + strcat(*ascii_output, temp_line); + + snprintf(temp_line, sizeof(temp_line), "Pad-Offset: %lu\n", offset); + strcat(*ascii_output, temp_line); + + strcat(*ascii_output, "\n"); + + // Add base64 data in 64-character lines + int b64_len = strlen(base64_data); + for (int i = 0; i < b64_len; i += 64) { + char line[70]; + snprintf(line, sizeof(line), "%.64s\n", base64_data + i); + strcat(*ascii_output, line); + } + + strcat(*ascii_output, "-----END OTP MESSAGE-----\n"); + + free(base64_data); + return 0; // Success +} + +// Universal pad integrity validator - consolidates pad validation logic +// Verifies pad checksum matches expected value for security +int validate_pad_integrity(const char* pad_path, const char* expected_chksum) { + if (!pad_path || !expected_chksum) { + return 1; // Error: null pointer + } + + char current_chksum[MAX_HASH_LENGTH]; + if (calculate_checksum(pad_path, current_chksum) != 0) { + return 2; // Error: cannot calculate checksum + } + + if (strcmp(expected_chksum, current_chksum) != 0) { + return 3; // Error: checksum mismatch + } + + return 0; // Success - pad integrity verified +} + +// Universal decrypt function - consolidates all decrypt operations +// input_data: encrypted message text or file path +// output_target: output file path (NULL for stdout/interactive) +// mode: determines behavior and output format +int universal_decrypt(const char* input_data, const char* output_target, decrypt_mode_t mode) { + char stored_chksum[MAX_HASH_LENGTH]; + uint64_t pad_offset; + char base64_data[MAX_INPUT_SIZE * 8] = {0}; + unsigned char* ciphertext = NULL; + int ciphertext_len; + + // Handle input based on mode + if (mode == DECRYPT_MODE_FILE_TO_TEXT || mode == DECRYPT_MODE_FILE_TO_FILE) { + // File input - read the entire file + FILE* input_fp = fopen(input_data, "r"); + if (!input_fp) { + printf("Error: Cannot open input file %s\n", input_data); + return 1; + } + + fseek(input_fp, 0, SEEK_END); + long file_size = ftell(input_fp); + fseek(input_fp, 0, SEEK_SET); + + char* file_content = malloc(file_size + 1); + if (!file_content) { + printf("Error: Memory allocation failed\n"); + fclose(input_fp); + return 1; + } + + size_t bytes_read = fread(file_content, 1, file_size, input_fp); + file_content[bytes_read] = '\0'; + fclose(input_fp); + + // Parse ASCII message from file content + if (parse_ascii_message(file_content, stored_chksum, &pad_offset, base64_data) != 0) { + printf("Error: Invalid ASCII armored format in file\n"); + free(file_content); + return 1; + } + free(file_content); + + if (mode == DECRYPT_MODE_FILE_TO_TEXT) { + printf("Decrypting ASCII armored file...\n"); + } + // Note: DECRYPT_MODE_FILE_TO_FILE should be completely silent for piping + } else { + // Text input (interactive or piped) + const char* message_text; + char full_message[MAX_INPUT_SIZE * 4] = {0}; + + if (input_data != NULL) { + message_text = input_data; + } else { + // Interactive mode - read from stdin + if (mode == DECRYPT_MODE_INTERACTIVE) { + printf("Enter encrypted message (paste the full ASCII armor block):\n"); + } + + char line[MAX_LINE_LENGTH]; + while (fgets(line, sizeof(line), stdin)) { + strncat(full_message, line, sizeof(full_message) - strlen(full_message) - 1); + if (strstr(line, "-----END OTP MESSAGE-----")) { + break; + } + } + message_text = full_message; + } + + // Parse ASCII message from text + if (parse_ascii_message(message_text, stored_chksum, &pad_offset, base64_data) != 0) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Invalid message format - missing BEGIN header\n"); + } else { + printf("Error: Invalid message format - missing BEGIN header\n"); + } + return 1; + } + } + + // Get pad path and check existence + char pad_path[MAX_HASH_LENGTH + 20]; + char state_path[MAX_HASH_LENGTH + 20]; + get_pad_path(stored_chksum, pad_path, state_path); + + if (access(pad_path, R_OK) != 0) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Required pad not found: %s\n", stored_chksum); + } else { + printf("Error: Required pad not found: %s\n", stored_chksum); + if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) { + printf("Available pads:\n"); + char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0); + if (selected) { + free(selected); + } + } + } + return 1; + } + + // Validate pad integrity + int integrity_result = validate_pad_integrity(pad_path, stored_chksum); + if (integrity_result == 3) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Pad integrity check failed!\n"); + return 1; + } else if (mode == DECRYPT_MODE_INTERACTIVE) { + printf("Warning: Pad integrity check failed!\n"); + printf("Expected: %s\n", stored_chksum); + printf("Continue anyway? (y/N): "); + fflush(stdout); + + char response[10]; + if (fgets(response, sizeof(response), stdin) == NULL || + (response[0] != 'y' && response[0] != 'Y')) { + printf("Decryption aborted.\n"); + return 1; + } + } + } else if (integrity_result != 0) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Cannot verify pad integrity\n"); + } else { + printf("Error: Cannot verify pad integrity\n"); + } + return 1; + } else { + if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) { + printf("Pad integrity: VERIFIED\n"); + } + } + + // Decode base64 ciphertext + ciphertext = custom_base64_decode(base64_data, &ciphertext_len); + if (!ciphertext) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Invalid base64 data\n"); + } else { + printf("Error: Invalid base64 data\n"); + } + return 1; + } + + // Load pad data using universal function + unsigned char* pad_data; + if (load_pad_data(stored_chksum, pad_offset, ciphertext_len, &pad_data) != 0) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Cannot load pad data\n"); + } else { + printf("Error: Cannot load pad data\n"); + } + free(ciphertext); + return 1; + } + + // Decrypt using universal XOR operation + if (universal_xor_operation(ciphertext, ciphertext_len, pad_data, ciphertext) != 0) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Decryption operation failed\n"); + } else { + printf("Error: Decryption operation failed\n"); + } + free(ciphertext); + free(pad_data); + return 1; + } + + // Output based on mode + if (mode == DECRYPT_MODE_FILE_TO_FILE) { + // Write to output file + const char* output_file = output_target; + + // Generate default output filename if not provided + char default_output[512]; + if (output_file == NULL) { + strncpy(default_output, input_data, sizeof(default_output) - 1); + default_output[sizeof(default_output) - 1] = '\0'; + + char* ext = strstr(default_output, ".otp.asc"); + if (ext) { + *ext = '\0'; + } else { + strncat(default_output, ".decrypted", sizeof(default_output) - strlen(default_output) - 1); + } + output_file = default_output; + } + + FILE* output_fp = fopen(output_file, "wb"); + if (!output_fp) { + printf("Error: Cannot create output file %s\n", output_file); + free(ciphertext); + free(pad_data); + return 1; + } + + if (fwrite(ciphertext, 1, ciphertext_len, output_fp) != (size_t)ciphertext_len) { + printf("Error: Cannot write decrypted data\n"); + free(ciphertext); + free(pad_data); + fclose(output_fp); + return 1; + } + fclose(output_fp); + + // Only show success messages in non-silent modes + if (mode != DECRYPT_MODE_FILE_TO_FILE) { + printf("File decrypted successfully: %s\n", output_file); + printf("Note: ASCII format does not preserve original filename/permissions\n"); + } + } else { + // Text output to stdout - need to allocate space for null terminator + char* decrypted_text = malloc(ciphertext_len + 1); + if (!decrypted_text) { + printf("Error: Memory allocation failed for output\n"); + free(ciphertext); + free(pad_data); + return 1; + } + + memcpy(decrypted_text, ciphertext, ciphertext_len); + decrypted_text[ciphertext_len] = '\0'; + + if (mode == DECRYPT_MODE_SILENT) { + // Silent mode - just output the text + printf("%s\n", decrypted_text); + fflush(stdout); + } else { + // Interactive mode - with label + printf("Decrypted: %s\n", decrypted_text); + } + + free(decrypted_text); + } + + // Cleanup + free(ciphertext); + free(pad_data); + + return 0; +} + +// Enhanced input function with editable pre-filled text +int get_filename_with_default(const char* prompt, const char* default_path, char* result, size_t result_size) { + // Find the last directory separator + char* last_slash = strrchr(default_path, '/'); + char directory[1024] = ""; + char filename[512] = ""; + + if (last_slash) { + // Extract directory path + size_t dir_len = last_slash - default_path + 1; // Include the trailing slash + if (dir_len < sizeof(directory)) { + strncpy(directory, default_path, dir_len); + directory[dir_len] = '\0'; + } + + // Extract filename + strncpy(filename, last_slash + 1, sizeof(filename) - 1); + filename[sizeof(filename) - 1] = '\0'; + } else { + // No directory separator, treat as filename only + strncpy(filename, default_path, sizeof(filename) - 1); + filename[sizeof(filename) - 1] = '\0'; + } + + // Setup terminal for raw input + struct termios orig_termios; + if (tcgetattr(STDIN_FILENO, &orig_termios) != 0) { + // Fallback to simple input if terminal control fails + printf("\n%s\n%s: ", prompt, directory); + fflush(stdout); + + char input_buffer[512]; + if (!fgets(input_buffer, sizeof(input_buffer), stdin)) { + return 1; + } + input_buffer[strcspn(input_buffer, "\n")] = 0; + + if (strlen(input_buffer) == 0) { + strncpy(result, default_path, result_size - 1); + } else { + if (strlen(directory) > 0) { + snprintf(result, result_size, "%s%s", directory, input_buffer); + } else { + strncpy(result, input_buffer, result_size - 1); + } + } + result[result_size - 1] = '\0'; + return 0; + } + + // Set up raw terminal mode + struct termios raw_termios = orig_termios; + raw_termios.c_lflag &= ~(ECHO | ICANON); + raw_termios.c_cc[VMIN] = 1; + raw_termios.c_cc[VTIME] = 0; + + if (tcsetattr(STDIN_FILENO, TCSANOW, &raw_termios) != 0) { + // Fallback if terminal setup fails + printf("\n%s\n%s: ", prompt, directory); + fflush(stdout); + + char input_buffer[512]; + if (!fgets(input_buffer, sizeof(input_buffer), stdin)) { + return 1; + } + input_buffer[strcspn(input_buffer, "\n")] = 0; + + if (strlen(input_buffer) == 0) { + strncpy(result, default_path, result_size - 1); + } else { + if (strlen(directory) > 0) { + snprintf(result, result_size, "%s%s", directory, input_buffer); + } else { + strncpy(result, input_buffer, result_size - 1); + } + } + result[result_size - 1] = '\0'; + return 0; + } + + // Display prompt and directory + printf("\n%s\n%s", prompt, directory); + fflush(stdout); + + // Initialize editing buffer with default filename + char edit_buffer[512]; + strncpy(edit_buffer, filename, sizeof(edit_buffer) - 1); + edit_buffer[sizeof(edit_buffer) - 1] = '\0'; + int cursor_pos = strlen(edit_buffer); + int buffer_len = cursor_pos; + + // Display initial filename + printf("%s", edit_buffer); + fflush(stdout); + + // Main editing loop + int c; + while ((c = getchar()) != EOF) { + if (c == '\n' || c == '\r') { + // Enter key - accept input + break; + } else if (c == 127 || c == 8) { + // Backspace/Delete + if (cursor_pos > 0) { + // Move everything after cursor one position left + memmove(&edit_buffer[cursor_pos - 1], &edit_buffer[cursor_pos], buffer_len - cursor_pos + 1); + cursor_pos--; + buffer_len--; + + // Redraw line: move cursor to start of filename area, clear to end, redraw buffer + printf("\r%s\033[K%s", directory, edit_buffer); + + // Position cursor correctly + if (cursor_pos < buffer_len) { + printf("\033[%dD", buffer_len - cursor_pos); + } + fflush(stdout); + } + } else if (c == 27) { + // Escape sequence (arrow keys, etc.) + int c1 = getchar(); + int c2 = getchar(); + if (c1 == '[') { + if (c2 == 'C' && cursor_pos < buffer_len) { + // Right arrow + cursor_pos++; + printf("\033[1C"); + fflush(stdout); + } else if (c2 == 'D' && cursor_pos > 0) { + // Left arrow + cursor_pos--; + printf("\033[1D"); + fflush(stdout); + } else if (c2 == 'H') { + // Home key + printf("\033[%dD", cursor_pos); + cursor_pos = 0; + fflush(stdout); + } else if (c2 == 'F') { + // End key + printf("\033[%dC", buffer_len - cursor_pos); + cursor_pos = buffer_len; + fflush(stdout); + } + } + } else if (c >= 32 && c <= 126) { + // Printable character + if (buffer_len < (int)sizeof(edit_buffer) - 1) { + // Move everything after cursor one position right + memmove(&edit_buffer[cursor_pos + 1], &edit_buffer[cursor_pos], buffer_len - cursor_pos + 1); + edit_buffer[cursor_pos] = c; + cursor_pos++; + buffer_len++; + + // Redraw from cursor position + printf("%c", c); + if (cursor_pos < buffer_len) { + // Print remaining characters and move cursor back + printf("%s\033[%dD", &edit_buffer[cursor_pos], buffer_len - cursor_pos); + } + fflush(stdout); + } + } + // Ignore other control characters + } + + // Restore terminal + tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); + printf("\n"); + + // Construct final result + if (buffer_len == 0) { + // Empty input, use default + strncpy(result, default_path, result_size - 1); + } else { + // Combine directory with edited filename + edit_buffer[buffer_len] = '\0'; + if (strlen(directory) > 0) { + snprintf(result, result_size, "%s%s", directory, edit_buffer); + } else { + strncpy(result, edit_buffer, result_size - 1); + } + } + result[result_size - 1] = '\0'; + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// EDITOR AND FILE MANAGER +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +char* get_preferred_editor(void) { + // Check EDITOR environment variable first + char* editor = getenv("EDITOR"); + if (editor && strlen(editor) > 0) { + // Verify the editor exists + char command[512]; + snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", editor); + if (system(command) == 0) { + return strdup(editor); + } + } + + // Check VISUAL environment variable + editor = getenv("VISUAL"); + if (editor && strlen(editor) > 0) { + char command[512]; + snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", editor); + if (system(command) == 0) { + return strdup(editor); + } + } + + // Try common editors in order of preference + const char* common_editors[] = {"vim", "vi", "nano", "emacs", "gedit", NULL}; + for (int i = 0; common_editors[i] != NULL; i++) { + char command[512]; + snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", common_editors[i]); + if (system(command) == 0) { + return strdup(common_editors[i]); + } + } + + return NULL; // No editor found +} + +char* get_preferred_file_manager(void) { + // Try file managers in order of preference + const char* file_managers[] = {"ranger", "fzf", "nnn", "lf", NULL}; + + for (int i = 0; file_managers[i] != NULL; i++) { + char command[512]; + snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", file_managers[i]); + if (system(command) == 0) { + return strdup(file_managers[i]); + } + } + + return NULL; // No file manager found +} + +int launch_text_editor(const char* initial_content, char* result_buffer, size_t buffer_size) { + char* editor = get_preferred_editor(); + if (!editor) { + printf("Error: No text editor found. Set EDITOR environment variable or install vim/nano.\n"); + return 1; + } + + // Create temporary file + char temp_filename[64]; + snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_edit_%ld.tmp", time(NULL)); + + // Write initial content to temp file if provided + if (initial_content && strlen(initial_content) > 0) { + FILE* temp_file = fopen(temp_filename, "w"); + if (temp_file) { + fputs(initial_content, temp_file); + fclose(temp_file); + } + } else { + // Create empty temp file + FILE* temp_file = fopen(temp_filename, "w"); + if (temp_file) { + fclose(temp_file); + } + } + + // Launch editor + printf("Opening %s for text editing...\n", editor); + char command[512]; + snprintf(command, sizeof(command), "%s %s", editor, temp_filename); + + int result = system(command); + + if (result == 0) { + // Read the edited content back + FILE* temp_file = fopen(temp_filename, "r"); + if (temp_file) { + size_t bytes_read = fread(result_buffer, 1, buffer_size - 1, temp_file); + result_buffer[bytes_read] = '\0'; + + // Remove trailing newline if present + if (bytes_read > 0 && result_buffer[bytes_read - 1] == '\n') { + result_buffer[bytes_read - 1] = '\0'; + } + + fclose(temp_file); + } else { + printf("Error: Cannot read edited content\n"); + free(editor); + unlink(temp_filename); + return 1; + } + } else { + printf("Editor exited with error or was cancelled\n"); + free(editor); + unlink(temp_filename); + return 1; + } + + // Clean up + unlink(temp_filename); + free(editor); + + return 0; +} + +int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size) { + char* fm = get_preferred_file_manager(); + if (!fm) { + printf("No file manager found. Please install ranger, fzf, nnn, or lf.\n"); + printf("Falling back to manual file path entry.\n"); + return 1; // Fall back to manual entry + } + + char temp_filename[64]; + snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_file_%ld.tmp", time(NULL)); + + char command[512]; + int result = 1; + + printf("Opening %s for file selection...\n", fm); + + if (strcmp(fm, "ranger") == 0) { + snprintf(command, sizeof(command), "cd '%s' && ranger --choosefile=%s", + start_directory ? start_directory : ".", temp_filename); + } else if (strcmp(fm, "fzf") == 0) { + snprintf(command, sizeof(command), "cd '%s' && find . -type f | fzf > %s", + start_directory ? start_directory : ".", temp_filename); + } else if (strcmp(fm, "nnn") == 0) { + snprintf(command, sizeof(command), "cd '%s' && nnn -p %s", + start_directory ? start_directory : ".", temp_filename); + } else if (strcmp(fm, "lf") == 0) { + snprintf(command, sizeof(command), "cd '%s' && lf -selection-path=%s", + start_directory ? start_directory : ".", temp_filename); + } + + result = system(command); + + if (result == 0 || result == 256) { // Some file managers return 256 on success + // Read selected file from temp file + FILE* temp_file = fopen(temp_filename, "r"); + if (temp_file) { + if (fgets(selected_file, buffer_size, temp_file)) { + // Remove trailing newline + selected_file[strcspn(selected_file, "\n\r")] = 0; + + // For relative paths from fzf, make absolute if needed + if (selected_file[0] == '.' && selected_file[1] == '/') { + char current_dir[512]; + if (getcwd(current_dir, sizeof(current_dir))) { + char abs_path[1024]; + snprintf(abs_path, sizeof(abs_path), "%s/%s", current_dir, selected_file + 2); + strncpy(selected_file, abs_path, buffer_size - 1); + selected_file[buffer_size - 1] = '\0'; + } + } + + fclose(temp_file); + unlink(temp_filename); + free(fm); + return 0; // Success + } + fclose(temp_file); + } + } + + // Clean up and indicate failure + unlink(temp_filename); + free(fm); + return 1; // Fall back to manual entry +} + +int handle_text_encrypt(void) { + printf("\n"); + print_centered_header("Text Encrypt", 0); + + // Launch text editor directly + char text_buffer[MAX_INPUT_SIZE]; + if (launch_text_editor(NULL, text_buffer, sizeof(text_buffer)) != 0) { + printf("Error: Could not launch text editor\n"); + return 1; + } + + if (strlen(text_buffer) == 0) { + printf("No text entered - canceling encryption\n"); + return 1; + } + + // Use unified pad selection + char* selected_pad = select_pad_interactive("Select Pad for Text Encryption", + "Select pad (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Text encryption cancelled.\n"); + return 1; + } + + int result = encrypt_text(selected_pad, text_buffer); + free(selected_pad); + return result; +} + +int handle_file_encrypt(void) { + printf("\n"); + print_centered_header("File Encrypt", 0); + + // Launch file manager directly + char input_file[512]; + if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) { + printf("Error: Could not launch file manager\n"); + return 1; + } + + // Check if file exists + if (access(input_file, R_OK) != 0) { + printf("Error: File '%s' not found or cannot be read\n", input_file); + return 1; + } + + // Use unified pad selection + char* selected_pad = select_pad_interactive("Select Pad for File Encryption", + "Select pad (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("File encryption cancelled.\n"); + return 1; + } + + // Ask for output format + printf("\nSelect output format:\n"); + printf("1. Binary (.otp) - preserves file permissions\n"); + printf("2. ASCII (.otp.asc) - text-safe format\n"); + printf("Enter choice (1-2): "); + + char format_input[10]; + if (!fgets(format_input, sizeof(format_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + int ascii_armor = (atoi(format_input) == 2) ? 1 : 0; + + // Generate default output filename + char default_output[1024]; // Increased buffer size to prevent truncation warnings + if (ascii_armor) { + snprintf(default_output, sizeof(default_output), "%s.otp.asc", input_file); + } else { + snprintf(default_output, sizeof(default_output), "%s.otp", input_file); + } + + // Use enhanced input function for output filename + char output_file[512]; + if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { + printf("Error: Failed to read input\n"); + return 1; + } + + const char* output_filename = output_file; + + int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor); + free(selected_pad); + return result; +} + + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// PADS +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int calculate_checksum(const char* filename, char* checksum_hex) { + FILE* file = fopen(filename, "rb"); + if (!file) { + return 1; + } + + unsigned char checksum[32]; + unsigned char buffer[64 * 1024]; // 64KB buffer for large files + size_t bytes_read; + + // Initialize checksum + memset(checksum, 0, 32); + size_t total_bytes = 0; + + // Calculate XOR checksum of entire file + while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { + // Process this chunk with XOR checksum + for (size_t i = 0; i < bytes_read; i++) { + unsigned char bucket = (total_bytes + i) % 32; + checksum[bucket] ^= buffer[i] ^ (((total_bytes + i) >> 8) & 0xFF) ^ + (((total_bytes + i) >> 16) & 0xFF) ^ (((total_bytes + i) >> 24) & 0xFF); + } + total_bytes += bytes_read; + } + + fclose(file); + + // Now encrypt the checksum with the first 32 bytes of the pad + fseek(file = fopen(filename, "rb"), 0, SEEK_SET); + unsigned char pad_key[32]; + if (fread(pad_key, 1, 32, file) != 32) { + fclose(file); + return 1; + } + fclose(file); + + // XOR encrypt the checksum with pad data to create unique identifier + unsigned char encrypted_checksum[32]; + for (int i = 0; i < 32; i++) { + encrypted_checksum[i] = checksum[i] ^ pad_key[i]; + } + + // Convert to hex string (64 characters) + for (int i = 0; i < 32; i++) { + sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]); + } + checksum_hex[64] = '\0'; + + return 0; +} + +// Calculate checksum with progress display for large files +int calculate_checksum_with_progress(const char* filename, char* checksum_hex, int display_progress, uint64_t file_size) { + FILE* file = fopen(filename, "rb"); + if (!file) { + return 1; + } + + unsigned char checksum[32]; + unsigned char buffer[64 * 1024]; // 64KB buffer for large files + size_t bytes_read; + + // Initialize checksum + memset(checksum, 0, 32); + size_t total_bytes = 0; + time_t start_time = time(NULL); + + // Calculate XOR checksum of entire file with progress + while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { + // Process this chunk with XOR checksum + for (size_t i = 0; i < bytes_read; i++) { + unsigned char bucket = (total_bytes + i) % 32; + checksum[bucket] ^= buffer[i] ^ (((total_bytes + i) >> 8) & 0xFF) ^ + (((total_bytes + i) >> 16) & 0xFF) ^ (((total_bytes + i) >> 24) & 0xFF); + } + total_bytes += bytes_read; + + // Show progress for large files (every 64MB or if display_progress is enabled) + if (display_progress && file_size > 10 * 1024 * 1024 && total_bytes % (64 * 1024 * 1024) == 0) { + show_progress(total_bytes, file_size, start_time); + } + } + + // Final progress update + if (display_progress && file_size > 10 * 1024 * 1024) { + show_progress(file_size, file_size, start_time); + printf("\n"); + } + + fclose(file); + + // Now encrypt the checksum with the first 32 bytes of the pad + fseek(file = fopen(filename, "rb"), 0, SEEK_SET); + unsigned char pad_key[32]; + if (fread(pad_key, 1, 32, file) != 32) { + fclose(file); + return 1; + } + fclose(file); + + // XOR encrypt the checksum with pad data to create unique identifier + unsigned char encrypted_checksum[32]; + for (int i = 0; i < 32; i++) { + encrypted_checksum[i] = checksum[i] ^ pad_key[i]; + } + + // Convert to hex string (64 characters) + for (int i = 0; i < 32; i++) { + sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]); + } + checksum_hex[64] = '\0'; + + return 0; +} + +// Unified pad selection function - extracts the best UI from handle_pads_menu() +char* select_pad_interactive(const char* title, const char* prompt, pad_filter_type_t filter_type, int allow_cancel) { + // Get list of pads from current directory + DIR* dir = opendir(current_pads_dir); + if (!dir) { + printf("Error: Cannot open pads directory %s\n", current_pads_dir); + return NULL; + } + + // Structure to store pad information + struct PadInfo { + char chksum[65]; + char size_str[32]; + char used_str[32]; + double percentage; + char location[256]; + }; + + struct PadInfo pads[100]; // Support up to 100 pads + int pad_count = 0; + + // Collect all pad information + struct dirent* entry; + while ((entry = readdir(dir)) != NULL && pad_count < 100) { + if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) { + strncpy(pads[pad_count].chksum, entry->d_name, 64); + pads[pad_count].chksum[64] = '\0'; + + // Get pad file size and usage info + char full_path[1024]; + snprintf(full_path, sizeof(full_path), "%s/%s", current_pads_dir, entry->d_name); + struct stat st; + if (stat(full_path, &st) == 0) { + // Get used bytes from state + uint64_t used_bytes; + read_state_offset(pads[pad_count].chksum, &used_bytes); + + // Apply filter + if (filter_type == PAD_FILTER_UNUSED_ONLY && used_bytes > 32) { + continue; // Skip used pads when filtering for unused only + } + + // Format total size + if (st.st_size < 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%luB", st.st_size); + } else if (st.st_size < 1024 * 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fKB", (double)st.st_size / 1024.0); + } else if (st.st_size < 1024 * 1024 * 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fMB", (double)st.st_size / (1024.0 * 1024.0)); + } else { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.2fGB", (double)st.st_size / (1024.0 * 1024.0 * 1024.0)); + } + + // Format used size + if (used_bytes < 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%luB", used_bytes); + } else if (used_bytes < 1024 * 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fKB", (double)used_bytes / 1024.0); + } else if (used_bytes < 1024 * 1024 * 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fMB", (double)used_bytes / (1024.0 * 1024.0)); + } else { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.2fGB", (double)used_bytes / (1024.0 * 1024.0 * 1024.0)); + } + + // Calculate percentage + pads[pad_count].percentage = (double)used_bytes / st.st_size * 100.0; + + // Set location info using directory display + get_directory_display(full_path, pads[pad_count].location, sizeof(pads[pad_count].location)); + + pad_count++; + } + } + } + closedir(dir); + + if (pad_count == 0) { + printf("\n%s\n", title); + if (filter_type == PAD_FILTER_UNUSED_ONLY) { + printf("No unused pads found.\n"); + printf("Entropy can only be added to pads with 0%% usage (only reserved bytes used).\n"); + } else { + printf("No pads found.\n"); + } + return NULL; + } + + // Calculate minimal unique prefixes for each pad + char prefixes[100][65]; + int prefix_lengths[100]; + + for (int i = 0; i < pad_count; i++) { + prefix_lengths[i] = 1; + + // Find minimal unique prefix + while (prefix_lengths[i] <= 64) { + int unique = 1; + + // Check if current prefix is unique among all other pads + for (int j = 0; j < pad_count; j++) { + if (i != j && strncmp(pads[i].chksum, pads[j].chksum, prefix_lengths[i]) == 0) { + unique = 0; + break; + } + } + + if (unique) { + break; + } + prefix_lengths[i]++; + } + + // Store the minimal prefix + strncpy(prefixes[i], pads[i].chksum, prefix_lengths[i]); + prefixes[i][prefix_lengths[i]] = '\0'; + } + + // Display title and pads table + printf("\n%s\n", title); + printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "ChkSum", "D", "Dir", "Size", "Used", "% Used"); + printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "--------", "--", "------------", "----------", "----------", "------"); + + // Get current default pad path for comparison + char* current_default = get_default_pad_path(); + char default_pad_checksum[65] = ""; + + if (current_default) { + // Extract checksum from default pad path + char* filename = strrchr(current_default, '/'); + if (!filename) filename = current_default; + else filename++; // Skip the '/' + + // Extract checksum (remove .pad extension) + if (strlen(filename) >= 68 && strstr(filename, ".pad")) { + strncpy(default_pad_checksum, filename, 64); + default_pad_checksum[64] = '\0'; + } + free(current_default); + } + + for (int i = 0; i < pad_count; i++) { + // Check if this is the default pad + int is_default = (strlen(default_pad_checksum) > 0 && + strncmp(pads[i].chksum, default_pad_checksum, 64) == 0); + + // Display first 8 characters of checksum with prefix underlined + char checksum_8char[9]; + strncpy(checksum_8char, pads[i].chksum, 8); + checksum_8char[8] = '\0'; + + printf("\033[4m%.*s\033[0m%s %-2s %-12s %-12s %-12s %.1f%%\n", + prefix_lengths[i], checksum_8char, // Underlined prefix + checksum_8char + prefix_lengths[i], // Rest of 8-char checksum + is_default ? "*" : "", // Default indicator + pads[i].location, + pads[i].size_str, + pads[i].used_str, + pads[i].percentage); + } + + // Display prompt + printf("\n%s", prompt); + if (allow_cancel) { + printf(" (or 'x' to cancel)"); + } + printf(": "); + + char input[MAX_HASH_LENGTH]; + if (!fgets(input, sizeof(input), stdin)) { + printf("Error: Failed to read input\n"); + return NULL; + } + input[strcspn(input, "\n")] = 0; + + // Handle empty input - select default pad if available + if (strlen(input) == 0) { + // Get current default pad path + char* current_default = get_default_pad_path(); + if (current_default) { + // Extract checksum from default pad path + char* filename = strrchr(current_default, '/'); + if (!filename) filename = current_default; + else filename++; // Skip the '/' + + // Extract checksum (remove .pad extension) + if (strlen(filename) >= 68 && strstr(filename, ".pad")) { + char default_checksum[65]; + strncpy(default_checksum, filename, 64); + default_checksum[64] = '\0'; + + // Verify this default pad is in our current list + for (int i = 0; i < pad_count; i++) { + if (strncmp(pads[i].chksum, default_checksum, 64) == 0) { + free(current_default); + printf("Selected default pad: %.16s...\n\n", default_checksum); + return strdup(default_checksum); + } + } + } + free(current_default); + } + // No default pad or default pad not in current list + printf("No default pad available or default pad not in current list\n"); + return NULL; + } + + // Handle cancel + if (allow_cancel && (toupper(input[0]) == 'X' && strlen(input) == 1)) { + return NULL; + } + + // Find matching pad by prefix only + int selected_pad = -1; + int match_count = 0; + + // Try prefix matching only + for (int i = 0; i < pad_count; i++) { + if (strncmp(input, pads[i].chksum, strlen(input)) == 0) { + if (match_count == 0) { + selected_pad = i; + } + match_count++; + } + } + + if (match_count == 0) { + printf("No pad found matching '%s'\n", input); + return NULL; + } else if (match_count > 1) { + printf("Ambiguous prefix. Multiple matches found.\n"); + return NULL; + } + + // Return selected pad checksum (caller must free) + return strdup(pads[selected_pad].chksum); +} + +int handle_pads_menu(void) { + printf("\n"); + print_centered_header("Pad Management", 0); + + // Get list of pads from current directory + DIR* dir = opendir(current_pads_dir); + if (!dir) { + printf("Error: Cannot open pads directory %s\n", current_pads_dir); + return 1; + } + + // Structure to store pad information + struct PadInfo { + char chksum[65]; + char size_str[32]; + char used_str[32]; + double percentage; + char location[256]; // Store location info + }; + + struct PadInfo pads[100]; // Support up to 100 pads + int pad_count = 0; + + // Collect all pad information + struct dirent* entry; + while ((entry = readdir(dir)) != NULL && pad_count < 100) { + if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) { + strncpy(pads[pad_count].chksum, entry->d_name, 64); + pads[pad_count].chksum[64] = '\0'; + + // Get pad file size and usage info + char full_path[1024]; // Increased buffer size + snprintf(full_path, sizeof(full_path), "%s/%s", current_pads_dir, entry->d_name); + struct stat st; + if (stat(full_path, &st) == 0) { + // Get used bytes from state + uint64_t used_bytes; + read_state_offset(pads[pad_count].chksum, &used_bytes); + + // Format total size + if (st.st_size < 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%luB", st.st_size); + } else if (st.st_size < 1024 * 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fKB", (double)st.st_size / 1024.0); + } else if (st.st_size < 1024 * 1024 * 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fMB", (double)st.st_size / (1024.0 * 1024.0)); + } else { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.2fGB", (double)st.st_size / (1024.0 * 1024.0 * 1024.0)); + } + + // Format used size + if (used_bytes < 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%luB", used_bytes); + } else if (used_bytes < 1024 * 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fKB", (double)used_bytes / 1024.0); + } else if (used_bytes < 1024 * 1024 * 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fMB", (double)used_bytes / (1024.0 * 1024.0)); + } else { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.2fGB", (double)used_bytes / (1024.0 * 1024.0 * 1024.0)); + } + + // Calculate percentage + pads[pad_count].percentage = (double)used_bytes / st.st_size * 100.0; + + // Set location info using directory display + get_directory_display(full_path, pads[pad_count].location, sizeof(pads[pad_count].location)); + + pad_count++; + } + } + } + closedir(dir); + + if (pad_count == 0) { + printf("No pads found.\n"); + printf("\nOptions:\n"); + printf(" \033[4mG\033[0menerate new pad\n"); + printf(" E\033[4mx\033[0mit\n"); + printf("\nSelect option: "); + + char input[10]; + if (fgets(input, sizeof(input), stdin)) { + char choice = toupper(input[0]); + if (choice == 'G') { + int result = handle_generate_menu(); + if (result == 0) { + // After successful pad generation, return to pads menu + return handle_pads_menu(); + } + return result; + } + } + return 0; + } + + // Calculate minimal unique prefixes for each pad + char prefixes[100][65]; // Store the minimal prefix for each pad + int prefix_lengths[100]; // Length of minimal prefix for each pad + + for (int i = 0; i < pad_count; i++) { + prefix_lengths[i] = 1; + + // Find minimal unique prefix + while (prefix_lengths[i] <= 64) { + int unique = 1; + + // Check if current prefix is unique among all other pads + for (int j = 0; j < pad_count; j++) { + if (i != j && strncmp(pads[i].chksum, pads[j].chksum, prefix_lengths[i]) == 0) { + unique = 0; + break; + } + } + + if (unique) { + break; + } + prefix_lengths[i]++; + } + + // Store the minimal prefix + strncpy(prefixes[i], pads[i].chksum, prefix_lengths[i]); + prefixes[i][prefix_lengths[i]] = '\0'; + } + + // Display pads with minimal prefixes underlined and default indicator + printf("\nAvailable pads:\n"); + printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "ChkSum", "D", "Dir", "Size", "Used", "% Used"); + printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "--------", "--", "------------", "----------", "----------", "------"); + + // Get current default pad path for comparison + char* current_default = get_default_pad_path(); + char default_pad_checksum[65] = ""; + + if (current_default) { + // Extract checksum from default pad path + char* filename = strrchr(current_default, '/'); + if (!filename) filename = current_default; + else filename++; // Skip the '/' + + // Extract checksum (remove .pad extension) + if (strlen(filename) >= 68 && strstr(filename, ".pad")) { + strncpy(default_pad_checksum, filename, 64); + default_pad_checksum[64] = '\0'; + } + free(current_default); + } + + for (int i = 0; i < pad_count; i++) { + // Check if this is the default pad + int is_default = (strlen(default_pad_checksum) > 0 && + strncmp(pads[i].chksum, default_pad_checksum, 64) == 0); + + // Display first 8 characters of checksum with prefix underlined + char checksum_8char[9]; + strncpy(checksum_8char, pads[i].chksum, 8); + checksum_8char[8] = '\0'; + + printf("\033[4m%.*s\033[0m%s %-2s %-12s %-12s %-12s %.1f%%\n", + prefix_lengths[i], checksum_8char, // Underlined prefix + checksum_8char + prefix_lengths[i], // Rest of 8-char checksum + is_default ? "*" : "", // Default indicator + pads[i].location, // Use the stored location info + pads[i].size_str, + pads[i].used_str, + pads[i].percentage); + } + + printf("\nActions:\n"); + printf(" \033[4mG\033[0menerate new pad\n"); + printf(" \033[4mA\033[0mdd entropy to pad\n"); + printf(" \033[4mV\033[0merify pad integrity\n"); + printf(" \033[4mD\033[0melete pad\n"); + printf(" \033[4mS\033[0met default pad\n"); + printf(" E\033[4mx\033[0mit\n"); + printf("\nSelect action: "); + + char input[MAX_HASH_LENGTH]; + if (!fgets(input, sizeof(input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + input[strcspn(input, "\n")] = 0; + + // Handle actions + if (toupper(input[0]) == 'G') { + int result = handle_generate_menu(); + if (result == 0) { + // After successful pad generation, return to pads menu + return handle_pads_menu(); + } + return result; + } else if (toupper(input[0]) == 'A') { + // Add entropy to pad - use unified function with unused pads filter + char* selected_pad = select_pad_interactive("Select Unused Pad for Entropy Addition", + "Select unused pad (by prefix)", + PAD_FILTER_UNUSED_ONLY, 1); + if (!selected_pad) { + printf("Entropy addition cancelled.\n"); + return handle_pads_menu(); + } + + // Add entropy to the selected unused pad + int result = handle_add_entropy_to_pad(selected_pad); + free(selected_pad); + if (result == 0) { + // Return to pads menu after successful entropy addition + return handle_pads_menu(); + } + return result; + } else if (toupper(input[0]) == 'V') { + // Verify pad integrity - use unified function + char* selected_pad = select_pad_interactive("Select Pad for Verification", + "Select pad to verify (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Pad verification cancelled.\n"); + return handle_pads_menu(); + } + + // Verify the selected pad + handle_verify_pad(selected_pad); + free(selected_pad); + return handle_pads_menu(); // Always return to pads menu after verification + } else if (toupper(input[0]) == 'D') { + // Delete pad - use unified function + char* selected_pad = select_pad_interactive("Select Pad for Deletion", + "Select pad to delete (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Pad deletion cancelled.\n"); + return handle_pads_menu(); + } + + // Delete the selected pad + handle_delete_pad(selected_pad); + free(selected_pad); + return handle_pads_menu(); // Always return to pads menu after deletion attempt + } else if (toupper(input[0]) == 'S') { + // Set default pad - use unified function + char* selected_pad = select_pad_interactive("Select Default Pad", + "Select pad to set as default (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Default pad selection cancelled.\n"); + return handle_pads_menu(); + } + + // Construct the full absolute pad path and set as default + char new_default_path[1024]; + if (current_pads_dir[0] == '/') { + // Already absolute path + int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad); + if (ret >= (int)sizeof(new_default_path)) { + printf("Error: Path too long for default pad setting\n"); + free(selected_pad); + return handle_pads_menu(); + } + } else { + // Relative path - make it absolute + char current_dir[512]; + if (getcwd(current_dir, sizeof(current_dir))) { + int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s/%s.pad", current_dir, current_pads_dir, selected_pad); + if (ret >= (int)sizeof(new_default_path)) { + // Path was truncated, fall back to relative path + int ret2 = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad); + if (ret2 >= (int)sizeof(new_default_path)) { + printf("Error: Path too long for default pad setting\n"); + free(selected_pad); + return handle_pads_menu(); + } + } + } else { + // Fallback to relative path + int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad); + if (ret >= (int)sizeof(new_default_path)) { + printf("Error: Path too long for default pad setting\n"); + free(selected_pad); + return handle_pads_menu(); + } + } + } + + if (set_default_pad_path(new_default_path) == 0) { + printf("Default pad set to: %.16s...\n", selected_pad); + printf("Full path: %s\n", new_default_path); + } else { + printf("Error: Failed to update default pad preference\n"); + } + + free(selected_pad); + return handle_pads_menu(); + } else if (toupper(input[0]) == 'X') { + return 0; // Exit to main menu + } else { + printf("Invalid action. Please select G, A, S, or X.\n"); + return handle_pads_menu(); + } +} + +int handle_add_entropy_to_pad(const char* pad_chksum) { + char header_text[128]; + snprintf(header_text, sizeof(header_text), "Add Entropy to Pad: %.16s...", pad_chksum); + printf("\n"); + print_centered_header(header_text, 0); + + // Present entropy source selection menu with consistent formatting + printf("Select entropy source:\n"); + printf(" \033[4mK\033[0meyboard entropy - Random typing for entropy collection\n"); + printf(" \033[4mD\033[0mice/Coins/Cards - Manual input for high-quality entropy\n"); + printf(" \033[4mH\033[0mardware RNG - Hardware random number generators\n"); + printf(" \033[4mF\033[0mile - Load entropy from binary file\n"); + printf(" \033[4mT\033[0mest RNG Speed - Test TrueRNG/SwiftRNG device performance\n"); + printf(" E\033[4mx\033[0mit\n"); + printf("\nSelect option: "); + + char source_input[10]; + if (!fgets(source_input, sizeof(source_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + char choice = toupper(source_input[0]); + entropy_source_t entropy_source; + + switch (choice) { + case 'K': + entropy_source = ENTROPY_SOURCE_KEYBOARD; + break; + case 'D': + entropy_source = ENTROPY_SOURCE_DICE; + break; + case 'H': + // Hardware RNG selected - will handle after target_bytes selection + entropy_source = ENTROPY_SOURCE_TRUERNG; + break; + case 'F': + entropy_source = ENTROPY_SOURCE_FILE; + break; + case 'T': + // Test RNG speed - this doesn't collect entropy, just tests the device + return test_truerng_speed(); + case 'X': + return 0; // Exit + default: + printf("Invalid choice. Please select K, D, H, F, T, or X.\n"); + return 1; + } + + size_t target_bytes; + + // For TrueRNG, automatically use the full pad size + if (entropy_source == ENTROPY_SOURCE_TRUERNG) { + // Get the pad file size + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + struct stat pad_stat; + if (stat(pad_path, &pad_stat) != 0) { + printf("Error: Cannot get pad file size\n"); + return 1; + } + + target_bytes = (size_t)pad_stat.st_size; + printf("\nTrueRNG selected - will enhance entire pad with hardware entropy\n"); + printf("Pad size: %.2f GB (%zu bytes)\n", + (double)target_bytes / (1024.0 * 1024.0 * 1024.0), target_bytes); + } else { + // For other entropy sources, show the selection menu + printf("\nEntropy collection options:\n"); + printf(" 1. Recommended (2048 bytes) - Optimal security\n"); + printf(" 2. Minimum (1024 bytes) - Good security\n"); + printf(" 3. Maximum (4096 bytes) - Maximum security\n"); + printf(" 4. Custom amount\n"); + printf("Enter choice (1-4): "); + + char amount_input[10]; + if (!fgets(amount_input, sizeof(amount_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + target_bytes = 2048; // Default + int amount_choice = atoi(amount_input); + + switch (amount_choice) { + case 1: + target_bytes = 2048; + break; + case 2: + target_bytes = 1024; + break; + case 3: + target_bytes = 4096; + break; + case 4: + printf("Enter custom amount (512-8192 bytes): "); + char custom_input[32]; + if (!fgets(custom_input, sizeof(custom_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + size_t custom_amount = (size_t)atoi(custom_input); + if (custom_amount < 512 || custom_amount > 8192) { + printf("Error: Invalid amount. Must be between 512 and 8192 bytes.\n"); + return 1; + } + target_bytes = custom_amount; + break; + default: + target_bytes = 2048; // Default to recommended + break; + } + } + + // For TrueRNG, detect all devices and present selection menu + hardware_rng_device_t selected_device; + if (entropy_source == ENTROPY_SOURCE_TRUERNG) { + hardware_rng_device_t devices[10]; + int num_devices_found = 0; + + if (detect_all_hardware_rng_devices(devices, 10, &num_devices_found) != 0) { + printf("Error: Failed to detect hardware RNG devices\n"); + return 1; + } + + if (num_devices_found == 0) { + printf("No hardware RNG devices found.\n"); + printf("\nSupported devices:\n"); + printf(" - TrueRNG/SwiftRNG (PID: %s, VID: %s)\n", TRUERNG_VID, TRUERNG_PID); + printf(" - TrueRNGpro/SwiftRNGpro (PID: %s, VID: %s)\n", TRUERNGPRO_VID, TRUERNGPRO_PID); + printf(" - TrueRNGproV2/SwiftRNGproV2 (PID: %s, VID: %s)\n", TRUERNGPROV2_VID, TRUERNGPROV2_PID); + printf("\nPlease connect a TrueRNG or SwiftRNG device and try again.\n"); + return 1; + } + + // Present device selection menu + if (select_hardware_rng_device_interactive(devices, num_devices_found, &selected_device) != 0) { + printf("Device selection cancelled or failed.\n"); + return 1; + } + } + + // For TrueRNG with large pads, use streaming approach + if (entropy_source == ENTROPY_SOURCE_TRUERNG && target_bytes > MAX_ENTROPY_BUFFER) { + printf("\nUsing streaming approach for large pad enhancement...\n"); + + // Prompt user for entropy distribution mode + printf("Choose entropy distribution method:\n"); + printf("0) Direct XOR (recommended for high entropy)\n"); + printf("1) ChaCha20 distribution (recommended for low entropy)\n"); + printf("Enter choice (0 or 1): "); + fflush(stdout); + + char mode_input[10]; + if (fgets(mode_input, sizeof(mode_input), stdin) == NULL) { + printf("Error reading input\n"); + return 1; + } + + int entropy_mode = atoi(mode_input); + if (entropy_mode != 0 && entropy_mode != 1) { + printf("Invalid choice. Using ChaCha20 distribution (1).\n"); + entropy_mode = 1; + } + + // Use streaming collection with selected device + int result = collect_truerng_entropy_streaming_from_device(&selected_device, pad_chksum, target_bytes, 1, entropy_mode); + + if (result != 0) { + printf("Error: TrueRNG streaming entropy collection failed\n"); + return 1; + } + + // Update checksum after entropy addition + printf("\nšŸ”„ Updating pad checksum...\n"); + char new_chksum[65]; + int checksum_result = update_pad_checksum_after_entropy(pad_chksum, new_chksum); + + if (checksum_result == 0) { + printf("āœ“ Pad checksum updated successfully\n"); + printf(" Old checksum: %.16s...\n", pad_chksum); + printf(" New checksum: %.16s...\n", new_chksum); + printf("āœ“ Pad files renamed to new checksum\n"); + } else if (checksum_result == 2) { + printf("ℹ Checksum unchanged (unusual but not an error)\n"); + } else { + printf("⚠ Warning: Checksum update failed (entropy was added successfully)\n"); + printf(" You may need to manually handle the checksum update\n"); + return 1; + } + + printf("\nšŸŽ‰ SUCCESS! Your entire pad now has enhanced randomness!\n"); + + // Use enhanced pause mechanism instead of simple getchar + print_centered_header("Pad Enhancement Complete", 1); + + return 0; + } + + // For other entropy sources or smaller amounts, use traditional approach + printf("\nCollecting %zu bytes of entropy from selected source...\n", target_bytes); + + // Allocate entropy buffer + unsigned char* entropy_buffer = malloc(MAX_ENTROPY_BUFFER); + if (!entropy_buffer) { + printf("Error: Cannot allocate entropy buffer\n"); + return 1; + } + + // Collect entropy using unified interface + size_t collected_bytes = 0; + int result = collect_entropy_by_source(entropy_source, entropy_buffer, target_bytes, &collected_bytes, 1); + + if (result != 0) { + printf("Error: Entropy collection failed\n"); + free(entropy_buffer); + return 1; + } + + if (collected_bytes < 512) { + printf("Error: Insufficient entropy collected (%zu bytes)\n", collected_bytes); + free(entropy_buffer); + return 1; + } + + printf("\nProcessing entropy and modifying pad...\n"); + + // Add entropy to pad + result = add_entropy_to_pad(pad_chksum, entropy_buffer, collected_bytes, 1); + + // Clear entropy buffer for security + memset(entropy_buffer, 0, MAX_ENTROPY_BUFFER); + free(entropy_buffer); + + if (result != 0) { + printf("Error: Failed to add entropy to pad\n"); + return 1; + } + + printf("\nšŸŽ‰ SUCCESS! Your pad now has enhanced randomness!\n"); + + // Use enhanced pause mechanism instead of simple getchar + print_centered_header("Entropy Enhancement Complete", 1); + + return 0; +} + +int handle_verify_pad(const char* pad_chksum) { + char header_text[128]; + snprintf(header_text, sizeof(header_text), "Verify Pad Integrity: %.16s...", pad_chksum); + printf("\n"); + print_centered_header(header_text, 0); + + // Construct pad file path + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Check if pad file exists + if (access(pad_path, R_OK) != 0) { + printf("āŒ ERROR: Pad file not found: %s\n", pad_path); + return 1; + } + + printf("Calculating pad checksum...\n"); + + // Calculate actual checksum of the pad file + char calculated_checksum[65]; + if (calculate_checksum(pad_path, calculated_checksum) != 0) { + printf("āŒ ERROR: Failed to calculate pad checksum\n"); + return 1; + } + + // Compare calculated checksum with filename (expected checksum) + if (strcmp(pad_chksum, calculated_checksum) == 0) { + printf("āœ… SUCCESS: Pad integrity verified!\n"); + printf(" Expected: %.16s...\n", pad_chksum); + printf(" Actual: %.16s...\n", calculated_checksum); + printf(" Status: MATCH - Pad is intact and valid\n"); + + // Get additional pad info + struct stat pad_stat; + if (stat(pad_path, &pad_stat) == 0) { + uint64_t used_bytes; + read_state_offset(pad_chksum, &used_bytes); + + double size_gb = (double)pad_stat.st_size / (1024.0 * 1024.0 * 1024.0); + double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0); + double usage_percent = (double)used_bytes / pad_stat.st_size * 100.0; + + printf(" Size: %.2f GB (%lu bytes)\n", size_gb, pad_stat.st_size); + printf(" Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes); + printf(" Usage: %.1f%%\n", usage_percent); + } + + printf("\nāœ… This pad is safe to use for encryption.\n"); + + // Pause before returning to menu to let user see the verification results + print_centered_header("Pad Verification Complete", 1); + return 0; + } else { + printf("āŒ FAILURE: Pad integrity check failed!\n"); + printf(" Expected: %.16s...\n", pad_chksum); + printf(" Actual: %.16s...\n", calculated_checksum); + printf(" Status: MISMATCH - Pad may be corrupted!\n"); + printf("\nāš ļø WARNING: This pad should NOT be used for encryption.\n"); + printf(" The pad file may have been modified or corrupted.\n"); + printf(" Consider regenerating this pad or using a different one.\n"); + return 1; + } +} + +int handle_delete_pad(const char* pad_chksum) { + char header_text[128]; + snprintf(header_text, sizeof(header_text), "Delete Pad: %.16s...", pad_chksum); + printf("\n"); + print_centered_header(header_text, 0); + + // Construct pad and state file paths + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Check if pad file exists + if (access(pad_path, F_OK) != 0) { + printf("āŒ ERROR: Pad file not found: %s\n", pad_path); + return 1; + } + + // Get pad information for display + struct stat pad_stat; + if (stat(pad_path, &pad_stat) == 0) { + uint64_t used_bytes; + read_state_offset(pad_chksum, &used_bytes); + + double size_gb = (double)pad_stat.st_size / (1024.0 * 1024.0 * 1024.0); + double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0); + double usage_percent = (double)used_bytes / pad_stat.st_size * 100.0; + + printf("Pad Information:\n"); + printf(" Checksum: %s\n", pad_chksum); + printf(" Size: %.2f GB (%lu bytes)\n", size_gb, pad_stat.st_size); + printf(" Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes); + printf(" Usage: %.1f%%\n", usage_percent); + printf(" Path: %s\n", pad_path); + } + + // Check if this is the default pad + char* current_default = get_default_pad_path(); + int is_default_pad = 0; + if (current_default) { + // Check if the pad to be deleted is the current default + if (strstr(current_default, pad_chksum)) { + is_default_pad = 1; + printf(" Status: āš ļø This is your DEFAULT pad\n"); + } + free(current_default); + } + + // Warning and confirmation + printf("\nāš ļø WARNING: This action cannot be undone!\n"); + if (is_default_pad) { + printf("āš ļø Deleting the default pad will require setting a new default.\n"); + } + printf("\nAre you absolutely sure you want to delete this pad? (y/N): "); + + char response[10]; + if (!fgets(response, sizeof(response), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + // Require explicit 'y' or 'Y' to proceed + if (response[0] != 'y' && response[0] != 'Y') { + printf("Pad deletion cancelled.\n"); + return 0; // User cancelled - not an error + } + + // Double confirmation for extra safety + printf("\nFinal confirmation - type 'DELETE' to proceed: "); + char final_response[20]; + if (!fgets(final_response, sizeof(final_response), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + // Remove newline + final_response[strcspn(final_response, "\n")] = 0; + + if (strcmp(final_response, "DELETE") != 0) { + printf("Confirmation text did not match. Pad deletion cancelled.\n"); + return 0; // User didn't confirm - not an error + } + + // Proceed with deletion + printf("\nDeleting pad files...\n"); + + // Delete pad file + if (unlink(pad_path) != 0) { + printf("āŒ ERROR: Failed to delete pad file: %s\n", pad_path); + perror("unlink"); + return 1; + } else { + printf("āœ… Deleted pad file: %s\n", pad_path); + } + + // Delete state file (if it exists) + if (access(state_path, F_OK) == 0) { + if (unlink(state_path) != 0) { + printf("āš ļø WARNING: Failed to delete state file: %s\n", state_path); + perror("unlink"); + // Continue - pad file was deleted successfully + } else { + printf("āœ… Deleted state file: %s\n", state_path); + } + } else { + printf("ā„¹ļø No state file found (this is normal)\n"); + } + + // Handle default pad update if necessary + if (is_default_pad) { + printf("\nšŸ”„ Updating default pad preference...\n"); + + // Clear the current default pad + if (set_preference("default_pad", NULL) == 0) { + printf("āœ… Default pad preference cleared\n"); + printf("ā„¹ļø You can set a new default pad from the pad management menu\n"); + } else { + printf("āš ļø WARNING: Failed to clear default pad preference\n"); + printf(" You may need to manually update your configuration\n"); + } + } + + printf("\nāœ… SUCCESS: Pad deleted successfully!\n"); + printf(" Checksum: %.16s...\n", pad_chksum); + printf(" Both pad and state files have been removed\n"); + + // Pause before returning to menu to let user see the success message + print_centered_header("Pad Deletion Complete", 1); + + return 0; +} + + +void print_usage(const char* program_name) { + printf("OTP Cipher - One Time Pad Implementation v0.3.16\n"); + printf("Built for testing entropy system\n"); + printf("Usage:\n"); + printf(" %s - Interactive mode\n", program_name); + printf(" %s generate|-g - Generate new pad\n", program_name); + printf(" %s encrypt|-e [pad_checksum_prefix] [text] - Encrypt text\n", program_name); + printf(" %s decrypt|-d [encrypted_message] - Decrypt message\n", program_name); + printf(" %s -f [-a] [-o ] - Encrypt file\n", program_name); + printf(" %s list|-l - List available pads\n", program_name); + printf("\nFile Operations:\n"); + printf(" -f - Encrypt file (binary .otp format)\n"); + printf(" -f -a - Encrypt file (ASCII .otp.asc format)\n"); + printf(" -o - Specify output filename\n"); + printf("\nShort flags:\n"); + printf(" -g generate -e encrypt -d decrypt -l list -f file\n"); + printf("\nExamples:\n"); + printf(" %s -e 1a2b3c \"Hello world\" - Encrypt inline text\n", program_name); + printf(" %s -f document.pdf 1a2b - Encrypt file (binary)\n", program_name); + printf(" %s -f document.pdf 1a2b -a - Encrypt file (ASCII)\n", program_name); + printf(" %s -f document.pdf 1a2b -o secret.otp - Encrypt with custom output\n", program_name); + printf(" %s -d \"-----BEGIN OTP MESSAGE-----...\" - Decrypt message/file\n", program_name); + printf(" %s -d encrypted.otp.asc - Decrypt ASCII file\n", program_name); + printf(" %s -g 1GB - Generate 1GB pad\n", program_name); + printf(" %s -l - List pads\n", program_name); + printf("\nSize examples: 1GB, 5TB, 512MB, 2048 (bytes)\n"); + printf("Pad selection: Full chksum or prefix\n"); +} diff --git a/otp.c b/otp.c index eba8fa7..aba19f5 100644 --- a/otp.c +++ b/otp.c @@ -18,26 +18,6 @@ #include "nostr_chacha20.h" #include "otp.h" -// Custom base64 character set -static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static const int base64_decode_table[256] = { - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, - 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, - 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, - -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, - 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 -}; #define MAX_INPUT_SIZE 4096 #define MAX_LINE_LENGTH 1024 @@ -49,5587 +29,7 @@ static const int base64_decode_table[256] = { //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // GLOBAL VARIABLES -//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// -static char current_pads_dir[512] = DEFAULT_PADS_DIR; -static char default_pad_path[1024] = ""; -static int is_interactive_mode = 0; - -// Terminal dimensions -static int terminal_width = 80; // Default fallback width -static int terminal_height = 24; // Default fallback height - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// TERMINAL UI FUNCTIONS -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -// Initialize terminal dimensions -void init_terminal_dimensions(void) { - struct winsize ws; - - // Try to get actual terminal size - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0 && ws.ws_row > 0) { - terminal_width = ws.ws_col; - terminal_height = ws.ws_row; - } - // If ioctl fails, keep the default values (80x24) -} - -// Print centered header with = padding, screen clearing, and optional pause -void print_centered_header(const char* text, int pause_before_clear) { - if (!text) return; - - // Phase 1: Pause if requested - if (pause_before_clear) { - printf("\nPress Enter to continue..."); - fflush(stdout); - - // Wait for Enter key - int c; - while ((c = getchar()) != '\n' && c != EOF) { - // Consume any extra characters until newline - } - } - - // Phase 2: Clear screen using terminal height - for (int i = 0; i < terminal_height; i++) { - printf("\n"); - } - - // Phase 3: Display centered header (existing logic) - int text_len = strlen(text); - int available_width = terminal_width; - - // Ensure minimum spacing: at least 1 space on each side - int min_required = text_len + 4; // text + " " + text + " " (spaces around text) - - if (available_width < min_required) { - // Terminal too narrow - just print the text with minimal formatting - printf("=== %s ===\n", text); - return; - } - - // Calculate padding - int total_padding = available_width - text_len - 2; // -2 for spaces around text - int left_padding = total_padding / 2; - int right_padding = total_padding - left_padding; - - // Print the header - for (int i = 0; i < left_padding; i++) { - printf("="); - } - printf(" %s ", text); - for (int i = 0; i < right_padding; i++) { - printf("="); - } - printf("\n"); -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// MAIN -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -int main(int argc, char* argv[]) { - // Initialize terminal dimensions first - init_terminal_dimensions(); - - // Load preferences - load_preferences(); - - // Detect interactive mode: only true when running with no arguments - is_interactive_mode = (argc == 1); - - // Check for OTP thumb drive on startup - char otp_drive_path[512]; - if (detect_otp_thumb_drive(otp_drive_path, sizeof(otp_drive_path))) { - // Only show messages in interactive mode - if (is_interactive_mode) { - printf("Detected OTP thumb drive: %s\n", otp_drive_path); - printf("Using as default pads directory for this session.\n\n"); - } - strncpy(current_pads_dir, otp_drive_path, sizeof(current_pads_dir) - 1); - current_pads_dir[sizeof(current_pads_dir) - 1] = '\0'; - } - - if (is_interactive_mode) { - return interactive_mode(); - } else { - return command_line_mode(argc, argv); - } -} - - - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// COMMAND LINE MODE -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -int command_line_mode(int argc, char* argv[]) { - // Check for help flags first - if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--h") == 0 || - strcmp(argv[1], "-help") == 0 || strcmp(argv[1], "--help") == 0 || - strcmp(argv[1], "help") == 0) { - print_usage(argv[0]); - return 0; - } - - if (strcmp(argv[1], "generate") == 0 || strcmp(argv[1], "-g") == 0) { - if (argc != 3) { - printf("Usage: %s generate|-g \n", argv[0]); - printf("Size examples: 1024, 1GB, 5TB, 512MB\n"); - return 1; - } - uint64_t size = parse_size_string(argv[2]); - if (size == 0) { - printf("Error: Invalid size format\n"); - return 1; - } - return generate_pad(size, 1); // Use simplified pad generation - } - else if (strcmp(argv[1], "encrypt") == 0 || strcmp(argv[1], "-e") == 0) { - // Check for piped input first - if (has_stdin_data()) { - char* piped_text = read_stdin_text(); - if (piped_text) { - int result = pipe_mode(argc, argv, piped_text); - free(piped_text); - return result; - } - } - - if (argc < 2 || argc > 4) { - printf("Usage: %s encrypt|-e [pad_chksum_or_prefix] [text_to_encrypt]\n", argv[0]); - return 1; - } - - // Check if pad was specified or use default - const char* pad_identifier = NULL; - const char* text = NULL; - - if (argc == 2) { - // Just -e, use default pad, no text (interactive) - pad_identifier = NULL; - text = NULL; - } else if (argc == 3) { - // Could be -e or -e (using default pad) - // Check if default pad is available to determine interpretation - char* default_pad = get_default_pad_path(); - if (default_pad) { - // Default pad available, treat argument as text - pad_identifier = NULL; - text = argv[2]; - free(default_pad); - } else { - // No default pad, treat as pad identifier - pad_identifier = argv[2]; - text = NULL; - } - } else { - // argc == 4: -e - pad_identifier = argv[2]; - text = argv[3]; - } - - // If pad_identifier is NULL, we need to use default pad - if (pad_identifier == NULL) { - char* default_pad = get_default_pad_path(); - if (default_pad) { - // Extract checksum from default pad path - char* filename = strrchr(default_pad, '/'); - if (!filename) filename = default_pad; - else filename++; // Skip the '/' - - // Extract checksum (remove .pad extension) - if (strlen(filename) >= 68 && strstr(filename, ".pad")) { - static char default_checksum[65]; - strncpy(default_checksum, filename, 64); - default_checksum[64] = '\0'; - pad_identifier = default_checksum; - } - free(default_pad); - - // Call encrypt_text and return result - return encrypt_text(pad_identifier, text); - } else { - printf("Error: No default pad configured. Specify pad explicitly or configure default pad.\n"); - return 1; - } - } else { - // Explicit pad specified, normal operation - return encrypt_text(pad_identifier, text); - } - } - else if (strcmp(argv[1], "decrypt") == 0 || strcmp(argv[1], "-d") == 0) { - if (argc == 2) { - // Check for piped input first - if (has_stdin_data()) { - // Piped decrypt mode - read stdin and decrypt silently - char* piped_message = read_stdin_text(); - if (piped_message) { - int result = decrypt_text(NULL, piped_message); - free(piped_message); - return result; - } - } - // Interactive mode - no arguments needed - return decrypt_text(NULL, NULL); - } - else if (argc == 3) { - // Check if the argument looks like an encrypted message (starts with -----) - if (strncmp(argv[2], "-----BEGIN OTP MESSAGE-----", 27) == 0) { - // Inline decrypt with message only - use silent mode for command line - return decrypt_text(NULL, argv[2]); - } else { - // Check if it's a file (contains . or ends with known extensions) - if (strstr(argv[2], ".") != NULL) { - // Treat as file - return decrypt_file(argv[2], NULL); - } else { - // Interactive decrypt with pad hint (legacy support) - return decrypt_text(argv[2], NULL); - } - } - } - else if (argc == 4) { - // Check for -o flag for output file - if (strcmp(argv[2], "-o") == 0) { - printf("Usage: %s decrypt|-d [-o ]\n", argv[0]); - return 1; - } else { - // Legacy format: pad_chksum and message, or file with output - // Use silent mode for command line when message is provided - return decrypt_text(argv[2], argv[3]); - } - } - else if (argc == 5 && strcmp(argv[3], "-o") == 0) { - // File decryption with output: -d -o - return decrypt_file(argv[2], argv[4]); - } - else { - printf("Usage: %s decrypt|-d [encrypted_message|file] [-o output_file]\n", argv[0]); - printf(" %s decrypt|-d [encrypted_message] (pad info from message)\n", argv[0]); - return 1; - } - } - else if (strcmp(argv[1], "-f") == 0) { - // File encryption mode: -f [-a] [-o ] - if (argc < 4) { - printf("Usage: %s -f [-a] [-o ]\n", argv[0]); - return 1; - } - - const char* input_file = argv[2]; - const char* pad_prefix = argv[3]; - int ascii_armor = 0; - const char* output_file = NULL; - - // Parse optional flags - for (int i = 4; i < argc; i++) { - if (strcmp(argv[i], "-a") == 0) { - ascii_armor = 1; - } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { - output_file = argv[++i]; - } - } - - return encrypt_file(pad_prefix, input_file, output_file, ascii_armor); - } - else if (strcmp(argv[1], "list") == 0 || strcmp(argv[1], "-l") == 0) { - printf("Available pads:\n"); - char* selected = select_pad_interactive("Available pads:", "Select pad (or press Enter to exit)", PAD_FILTER_ALL, 0); - if (selected) { - free(selected); - } - return 0; - } - else { - print_usage(argv[0]); - return 1; - } -} - - - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// INTERACTIVE MODE -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -int interactive_mode(void) { - char input[10]; - printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); - while (1) { - show_main_menu(); - - if (!fgets(input, sizeof(input), stdin)) { - printf("Goodbye!\n"); - break; - } - - char choice = toupper(input[0]); - - switch (choice) { - case 'T': - handle_text_encrypt(); - break; - case 'F': - handle_file_encrypt(); - break; - case 'D': - handle_decrypt_menu(); - break; - case 'P': - handle_pads_menu(); - break; - case 'X': - case 'Q': - printf("Goodbye!\n"); - return 0; - default: - printf("Invalid choice. Please try again.\n"); - break; - } - } - - return 0; -} - - -void show_main_menu(void) { - printf("\n"); - print_centered_header("Main Menu - OTP v0.3.15", 0); - printf("\n"); - - printf(" \033[4mT\033[0mext encrypt\n"); //TEXT ENCRYPT - printf(" \033[4mF\033[0mile encrypt\n"); //FILE ENCRYPT - printf(" \033[4mD\033[0mecrypt\n"); //DECRYPT - printf(" \033[4mP\033[0mads\n"); //PADS - printf(" E\033[4mx\033[0mit\n"); //EXIT - printf("\nSelect option: "); -} - -int handle_generate_menu(void) { - printf("\n"); - print_centered_header("Generate New Pad", 0); - printf("Enter pad size (examples: 1GB, 5TB, 512MB, 2048): "); - - char size_input[64]; - if (!fgets(size_input, sizeof(size_input), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - - size_input[strcspn(size_input, "\n")] = 0; - uint64_t size = parse_size_string(size_input); - - if (size == 0) { - printf("Error: Invalid size format\n"); - return 1; - } - - double size_gb = (double)size / (1024.0 * 1024.0 * 1024.0); - printf("Generating %.2f GB pad...\n", size_gb); - printf("Note: Use 'Add entropy' in Pads menu to enhance randomness after creation.\n"); - - return generate_pad(size, 1); -} - -int handle_encrypt_menu(void) { - printf("\n"); - print_centered_header("Encrypt Data", 0); - - printf("Available pads:\n"); - char* selected = select_pad_interactive("Available pads:", "Select pad (or press Enter to continue)", PAD_FILTER_ALL, 0); - int pad_count = 1; // Assume at least 1 pad if function returned - if (selected) { - free(selected); - } else { - pad_count = 0; - } - if (pad_count == 0) { - printf("No pads available. Generate a pad first.\n"); - return 1; - } - - // Ask user to choose between text and file encryption - printf("\nSelect encryption type:\n"); - printf(" 1. Text message\n"); - printf(" 2. File\n"); - printf("Enter choice (1-2): "); - - char choice_input[10]; - if (!fgets(choice_input, sizeof(choice_input), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - - int choice = atoi(choice_input); - - if (choice == 1) { - // Text encryption - use unified pad selection - char* selected_pad = select_pad_interactive("Select Pad for Text Encryption", - "Select pad (by prefix)", - PAD_FILTER_ALL, 1); - if (!selected_pad) { - printf("Text encryption cancelled.\n"); - return 1; - } - - int result = encrypt_text(selected_pad, NULL); // NULL for interactive mode - free(selected_pad); - return result; - } - else if (choice == 2) { - // File encryption - printf("\nFile selection options:\n"); - printf(" 1. Type file path directly\n"); - printf(" 2. Use file manager\n"); - printf("Enter choice (1-2): "); - - char file_choice[10]; - char input_file[512]; - - if (!fgets(file_choice, sizeof(file_choice), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - - if (atoi(file_choice) == 2) { - // Use file manager - if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) { - printf("Falling back to manual file path entry.\n"); - printf("Enter input file path: "); - if (!fgets(input_file, sizeof(input_file), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - input_file[strcspn(input_file, "\n")] = 0; - } - } else { - // Direct file path input - printf("Enter input file path: "); - if (!fgets(input_file, sizeof(input_file), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - input_file[strcspn(input_file, "\n")] = 0; - } - - // Check if file exists - if (access(input_file, R_OK) != 0) { - printf("Error: File '%s' not found or cannot be read\n", input_file); - return 1; - } - - // Use unified pad selection - char* selected_pad = select_pad_interactive("Select Pad for File Encryption", - "Select pad (by prefix)", - PAD_FILTER_ALL, 1); - if (!selected_pad) { - printf("File encryption cancelled.\n"); - return 1; - } - - // Ask for output format - printf("\nSelect output format:\n"); - printf(" 1. Binary (.otp) - preserves file permissions\n"); - printf(" 2. ASCII (.otp.asc) - text-safe format\n"); - printf("Enter choice (1-2): "); - - char format_input[10]; - if (!fgets(format_input, sizeof(format_input), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - - int ascii_armor = (atoi(format_input) == 2) ? 1 : 0; - - // Generate default output filename with files directory and use enhanced input function - char default_output[1024]; // Increased size to prevent truncation warnings - char temp_default[1024]; - - // Generate base filename with appropriate extension - if (ascii_armor) { - snprintf(temp_default, sizeof(temp_default), "%s.otp.asc", input_file); - } else { - snprintf(temp_default, sizeof(temp_default), "%s.otp", input_file); - } - - // Apply files directory default path - get_default_file_path(temp_default, default_output, sizeof(default_output)); - - char output_file[512]; - if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { - printf("Error: Failed to read input\n"); - return 1; - } - - const char* output_filename = output_file; - - int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor); - free(selected_pad); - return result; - } - else { - printf("Invalid choice. Please enter 1 or 2.\n"); - return 1; - } -} - -int handle_decrypt_menu(void) { - printf("\n"); - print_centered_header("Smart Decrypt", 0); - printf("Enter encrypted data (paste ASCII armor), file path, or press Enter to browse files:\n"); - - char input_line[MAX_LINE_LENGTH]; - if (!fgets(input_line, sizeof(input_line), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - - // Remove newline - input_line[strcspn(input_line, "\n")] = 0; - - if (strlen(input_line) == 0) { - // Empty input - launch file manager to browse for files - char selected_file[512]; - if (launch_file_manager(get_files_directory(), selected_file, sizeof(selected_file)) != 0) { - printf("Error: Could not launch file manager\n"); - return 1; - } - - // Generate smart default output filename with files directory and use enhanced input function - char temp_default[512]; - char default_output[512]; - strncpy(temp_default, selected_file, sizeof(temp_default) - 1); - temp_default[sizeof(temp_default) - 1] = '\0'; - - // Remove common encrypted extensions to get a better default - if (strstr(temp_default, ".otp.asc")) { - // Replace .otp.asc with original extension or no extension - char* ext_pos = strstr(temp_default, ".otp.asc"); - *ext_pos = '\0'; - } else if (strstr(temp_default, ".otp")) { - // Replace .otp with original extension or no extension - char* ext_pos = strstr(temp_default, ".otp"); - *ext_pos = '\0'; - } else { - // No recognized encrypted extension, add .decrypted suffix - strncat(temp_default, ".decrypted", sizeof(temp_default) - strlen(temp_default) - 1); - } - - // Apply files directory default path - get_default_file_path(temp_default, default_output, sizeof(default_output)); - - char output_file[512]; - if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { - printf("Error: Failed to read input\n"); - return 1; - } - - return decrypt_file(selected_file, output_file); - } - else if (strncmp(input_line, "-----BEGIN OTP MESSAGE-----", 27) == 0) { - // Looks like ASCII armor - collect the full message - char full_message[MAX_INPUT_SIZE * 4] = {0}; - strcat(full_message, input_line); - strcat(full_message, "\n"); - - printf("Continue pasting the message (end with -----END OTP MESSAGE-----):\n"); - - char line[MAX_LINE_LENGTH]; - while (fgets(line, sizeof(line), stdin)) { - strncat(full_message, line, sizeof(full_message) - strlen(full_message) - 1); - if (strstr(line, "-----END OTP MESSAGE-----")) { - break; - } - } - - return decrypt_text(NULL, full_message); - } - else { - // Check if it looks like a file path - if (access(input_line, R_OK) == 0) { - // It's a valid file - decrypt it with enhanced input for output filename - char temp_default[512]; - char default_output[512]; - strncpy(temp_default, input_line, sizeof(temp_default) - 1); - temp_default[sizeof(temp_default) - 1] = '\0'; - - // Remove common encrypted extensions to get a better default - if (strstr(temp_default, ".otp.asc")) { - // Replace .otp.asc with original extension or no extension - char* ext_pos = strstr(temp_default, ".otp.asc"); - *ext_pos = '\0'; - } else if (strstr(temp_default, ".otp")) { - // Replace .otp with original extension or no extension - char* ext_pos = strstr(temp_default, ".otp"); - *ext_pos = '\0'; - } else { - // No recognized encrypted extension, add .decrypted suffix - strncat(temp_default, ".decrypted", sizeof(temp_default) - strlen(temp_default) - 1); - } - - // Apply files directory default path - get_default_file_path(temp_default, default_output, sizeof(default_output)); - - char output_file[512]; - if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { - printf("Error: Failed to read input\n"); - return 1; - } - - return decrypt_file(input_line, output_file); - } else { - printf("Input not recognized as ASCII armor or valid file path.\n"); - return 1; - } - } -} - -uint64_t parse_size_string(const char* size_str) { - if (!size_str) return 0; - - char* endptr; - double value = strtod(size_str, &endptr); - - if (value <= 0) return 0; - - // Skip whitespace - while (*endptr && isspace(*endptr)) endptr++; - - uint64_t multiplier = 1; - - if (*endptr) { - char unit[4]; - strncpy(unit, endptr, 3); - unit[3] = '\0'; - - // Convert to uppercase - for (int i = 0; unit[i]; i++) { - unit[i] = toupper(unit[i]); - } - - if (strcmp(unit, "K") == 0 || strcmp(unit, "KB") == 0) { - multiplier = 1024ULL; - } else if (strcmp(unit, "M") == 0 || strcmp(unit, "MB") == 0) { - multiplier = 1024ULL * 1024ULL; - } else if (strcmp(unit, "G") == 0 || strcmp(unit, "GB") == 0) { - multiplier = 1024ULL * 1024ULL * 1024ULL; - } else if (strcmp(unit, "T") == 0 || strcmp(unit, "TB") == 0) { - multiplier = 1024ULL * 1024ULL * 1024ULL * 1024ULL; - } else { - return 0; // Invalid unit - } - } else { - // No unit specified, treat as bytes - multiplier = 1; - } - - return (uint64_t)(value * multiplier); -} - -char* find_pad_by_prefix(const char* prefix) { - DIR* dir = opendir(current_pads_dir); - if (!dir) { - printf("Error: Cannot open pads directory %s\n", current_pads_dir); - return NULL; - } - - struct dirent* entry; - char* matches[100]; // Store up to 100 matches - int match_count = 0; - - // Always try hex prefix matching first - size_t prefix_len = strlen(prefix); - while ((entry = readdir(dir)) != NULL && match_count < 100) { - // Skip . and .. entries, and only process .pad files - if (entry->d_name[0] == '.') continue; - if (!strstr(entry->d_name, ".pad")) continue; - if (strlen(entry->d_name) != 68) continue; // 64 char chksum + ".pad" - - // Compare prefix with the filename (checksum part) - if (strncmp(entry->d_name, prefix, prefix_len) == 0) { - matches[match_count] = malloc(65); - strncpy(matches[match_count], entry->d_name, 64); - matches[match_count][64] = '\0'; - match_count++; - } - } - - // Only prefix matching is supported - - closedir(dir); - - if (match_count == 0) { - printf("No pads found matching '%s'\n", prefix); - printf("Available pads:\n"); - char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0); - if (selected) { - free(selected); - } - return NULL; - } else if (match_count == 1) { - char* result = matches[0]; - return result; - } else { - printf("Multiple matches found for '%s':\n", prefix); - for (int i = 0; i < match_count; i++) { - printf(" %.16s...\n", matches[i]); - } - printf("Please be more specific with your prefix.\n"); - - // Free all matches and return NULL - for (int i = 0; i < match_count; i++) { - free(matches[i]); - } - return NULL; - } -} - - - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// PADS MENU -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - -int show_pad_info(const char* chksum) { - char pad_filename[MAX_HASH_LENGTH + 10]; - char state_filename[MAX_HASH_LENGTH + 10]; - - snprintf(pad_filename, sizeof(pad_filename), "%s.pad", chksum); - snprintf(state_filename, sizeof(state_filename), "%s.state", chksum); - - struct stat st; - if (stat(pad_filename, &st) != 0) { - printf("Pad not found: %s\n", chksum); - return 1; - } - - uint64_t used_bytes; - read_state_offset(chksum, &used_bytes); - - print_centered_header("Pad Information", 0); - printf("ChkSum: %s\n", chksum); - printf("File: %s\n", pad_filename); - - double size_gb = (double)st.st_size / (1024.0 * 1024.0 * 1024.0); - double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0); - double remaining_gb = (double)(st.st_size - used_bytes) / (1024.0 * 1024.0 * 1024.0); - - printf("Total size: %.2f GB (%lu bytes)\n", size_gb, st.st_size); - printf("Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes); - printf("Remaining: %.2f GB (%lu bytes)\n", remaining_gb, st.st_size - used_bytes); - printf("Usage: %.1f%%\n", (double)used_bytes / st.st_size * 100.0); - - return 0; -} - - -void show_progress(uint64_t current, uint64_t total, time_t start_time) { - time_t now = time(NULL); - double elapsed = difftime(now, start_time); - - if (elapsed < 1.0) elapsed = 1.0; // Avoid division by zero - - double percentage = (double)current / total * 100.0; - double speed = (double)current / elapsed / (1024.0 * 1024.0); // MB/s - - uint64_t remaining_bytes = total - current; - double eta = remaining_bytes / (current / elapsed); - - printf("\rProgress: %.1f%% (%.1f MB/s, ETA: %.0fs) ", percentage, speed, eta); - fflush(stdout); -} - -int generate_pad(uint64_t size_bytes, int display_progress) { - // Ensure pads directory exists - if (ensure_pads_directory() != 0) { - printf("Error: Cannot create pads directory\n"); - return 1; - } - - char temp_filename[1024]; - char pad_path[MAX_HASH_LENGTH + 20]; - char state_path[MAX_HASH_LENGTH + 20]; - char chksum_hex[MAX_HASH_LENGTH]; - - // Create temporary filename in the pads directory to avoid cross-filesystem issues - snprintf(temp_filename, sizeof(temp_filename), "%s/temp_%ld.pad", current_pads_dir, time(NULL)); - - FILE* urandom = fopen("/dev/urandom", "rb"); - if (!urandom) { - printf("Error: Cannot open /dev/urandom\n"); - return 1; - } - - FILE* pad_file = fopen(temp_filename, "wb"); - if (!pad_file) { - printf("Error: Cannot create temporary pad file %s\n", temp_filename); - fclose(urandom); - return 1; - } - - unsigned char buffer[64 * 1024]; // 64KB buffer - uint64_t bytes_written = 0; - time_t start_time = time(NULL); - - if (display_progress) { - printf("Generating pad...\n"); - } - - while (bytes_written < size_bytes) { - uint64_t chunk_size = sizeof(buffer); - if (size_bytes - bytes_written < chunk_size) { - chunk_size = size_bytes - bytes_written; - } - - if (fread(buffer, 1, (size_t)chunk_size, urandom) != (size_t)chunk_size) { - printf("Error: Failed to read from /dev/urandom\n"); - fclose(urandom); - fclose(pad_file); - unlink(temp_filename); - return 1; - } - - if (fwrite(buffer, 1, (size_t)chunk_size, pad_file) != (size_t)chunk_size) { - printf("Error: Failed to write to pad file\n"); - fclose(urandom); - fclose(pad_file); - unlink(temp_filename); - return 1; - } - - bytes_written += chunk_size; - - if (display_progress && bytes_written % PROGRESS_UPDATE_INTERVAL == 0) { - show_progress(bytes_written, size_bytes, start_time); - } - } - - if (display_progress) { - show_progress(size_bytes, size_bytes, start_time); - printf("\n"); - } - - fclose(urandom); - fclose(pad_file); - - // Calculate XOR checksum of the pad file - if (display_progress) { - printf("Calculating pad checksum...\n"); - } - if (calculate_checksum_with_progress(temp_filename, chksum_hex, display_progress, size_bytes) != 0) { - printf("Error: Cannot calculate pad checksum\n"); - unlink(temp_filename); - return 1; - } - - // Get final paths in pads directory - get_pad_path(chksum_hex, pad_path, state_path); - - // Rename temporary file to final name (atomic operation within same directory) - if (rename(temp_filename, pad_path) != 0) { - printf("Error: Cannot rename temporary pad file to final name\n"); - unlink(temp_filename); - return 1; - } - - // Set pad file to read-only - if (chmod(pad_path, S_IRUSR) != 0) { - printf("Warning: Cannot set pad file to read-only\n"); - } - - // Initialize state file with offset 32 (first 32 bytes reserved for checksum encryption) - FILE* state_file = fopen(state_path, "wb"); - if (state_file) { - uint64_t reserved_bytes = 32; - fwrite(&reserved_bytes, sizeof(uint64_t), 1, state_file); - fclose(state_file); - } else { - printf("Error: Failed to create state file\n"); - unlink(pad_path); - return 1; - } - - double size_gb = (double)size_bytes / (1024.0 * 1024.0 * 1024.0); - printf("Generated pad: %s (%.2f GB)\n", pad_path, size_gb); - printf("Pad checksum: %s\n", chksum_hex); - printf("State file: %s\n", state_path); - printf("Pad file set to read-only\n"); - printf("Use 'Add entropy' in Pads menu to enhance randomness.\n"); - - // Pause before returning to menu to let user see the success message - print_centered_header("Pad Generation Complete", 1); - - return 0; -} - -// In-place pad entropy addition using Chacha20 -int add_entropy_to_pad(const char* pad_chksum, const unsigned char* entropy_data, - size_t entropy_size, int display_progress) { - if (!pad_chksum || !entropy_data || entropy_size < 512) { - printf("Error: Invalid entropy data or insufficient entropy\n"); - return 1; - } - - // Derive Chacha20 key and nonce from entropy - unsigned char key[32], nonce[12]; - if (derive_chacha20_params(entropy_data, entropy_size, key, nonce) != 0) { - printf("Error: Failed to derive Chacha20 parameters from entropy\n"); - return 1; - } - - // Get pad file path - char pad_path[1024]; - char state_path[1024]; - get_pad_path(pad_chksum, pad_path, state_path); - - // Check if pad exists and get size - struct stat pad_stat; - if (stat(pad_path, &pad_stat) != 0) { - printf("Error: Pad file not found: %s\n", pad_path); - return 1; - } - - uint64_t pad_size = pad_stat.st_size; - - // Open pad file for read/write - FILE* pad_file = fopen(pad_path, "r+b"); - if (!pad_file) { - printf("Error: Cannot open pad file for modification: %s\n", pad_path); - printf("Note: Pad files are read-only. Temporarily changing permissions...\n"); - - // Try to make writable temporarily - if (chmod(pad_path, S_IRUSR | S_IWUSR) != 0) { - printf("Error: Cannot change pad file permissions\n"); - return 1; - } - - pad_file = fopen(pad_path, "r+b"); - if (!pad_file) { - printf("Error: Still cannot open pad file for modification\n"); - // Restore read-only - chmod(pad_path, S_IRUSR); - return 1; - } - } - - if (display_progress) { - printf("Adding entropy to pad using Chacha20...\n"); - printf("Pad size: %.2f GB (%lu bytes)\n", (double)pad_size / (1024.0*1024.0*1024.0), pad_size); - } - - // Process pad in chunks - unsigned char buffer[64 * 1024]; // 64KB chunks - unsigned char keystream[64 * 1024]; - uint64_t offset = 0; - uint32_t counter = 0; - time_t start_time = time(NULL); - - while (offset < pad_size) { - size_t chunk_size = sizeof(buffer); - if (pad_size - offset < chunk_size) { - chunk_size = pad_size - offset; - } - - // Read current pad data - if (fread(buffer, 1, chunk_size, pad_file) != chunk_size) { - printf("Error: Cannot read pad data at offset %lu\n", offset); - fclose(pad_file); - chmod(pad_path, S_IRUSR); // Restore read-only - return 1; - } - - // Generate keystream for this chunk - if (chacha20_encrypt(key, counter, nonce, buffer, keystream, chunk_size) != 0) { - printf("Error: Chacha20 keystream generation failed\n"); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - return 1; - } - - // XOR existing pad with keystream (adds entropy) - for (size_t i = 0; i < chunk_size; i++) { - buffer[i] ^= keystream[i]; - } - - // Seek back and write modified data - if (fseek(pad_file, offset, SEEK_SET) != 0) { - printf("Error: Cannot seek to offset %lu\n", offset); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - return 1; - } - - if (fwrite(buffer, 1, chunk_size, pad_file) != chunk_size) { - printf("Error: Cannot write modified pad data\n"); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - return 1; - } - - offset += chunk_size; - counter += (chunk_size + 63) / 64; // Round up for block count - - // Show progress for large pads - if (display_progress && offset % (64 * 1024 * 1024) == 0) { // Every 64MB - show_progress(offset, pad_size, start_time); - } - } - - fclose(pad_file); - - // Restore read-only permissions - if (chmod(pad_path, S_IRUSR) != 0) { - printf("Warning: Cannot restore pad file to read-only\n"); - } - - if (display_progress) { - show_progress(pad_size, pad_size, start_time); - printf("\nāœ“ Entropy successfully added to pad using Chacha20\n"); - printf("āœ“ Pad integrity maintained\n"); - printf("āœ“ %zu bytes of entropy distributed across entire pad\n", entropy_size); - printf("āœ“ Pad restored to read-only mode\n"); - - // Update checksum after entropy addition - printf("\nšŸ”„ Updating pad checksum...\n"); - char new_chksum[65]; - int checksum_result = update_pad_checksum_after_entropy(pad_chksum, new_chksum); - - if (checksum_result == 0) { - printf("āœ“ Pad checksum updated successfully\n"); - printf(" Old checksum: %.16s...\n", pad_chksum); - printf(" New checksum: %.16s...\n", new_chksum); - printf("āœ“ Pad files renamed to new checksum\n"); - - // Pause before returning to menu to let user see the success message - print_centered_header("Entropy Addition Complete", 1); - } else if (checksum_result == 2) { - printf("ℹ Checksum unchanged (unusual but not an error)\n"); - } else { - printf("⚠ Warning: Checksum update failed (entropy was added successfully)\n"); - printf(" You may need to manually handle the checksum update\n"); - return 1; // Report error despite successful entropy addition - } - } - - return 0; -} - - - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// ENCRYPT AND DECRYPT -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - -int encrypt_text(const char* pad_identifier, const char* input_text) { - char* pad_chksum = find_pad_by_prefix(pad_identifier); - if (!pad_chksum) { - return 1; - } - - char text_buffer[MAX_INPUT_SIZE]; - char chksum_hex[MAX_HASH_LENGTH]; - uint64_t current_offset; - - char pad_path[MAX_HASH_LENGTH + 20]; - char state_path[MAX_HASH_LENGTH + 20]; - get_pad_path(pad_chksum, pad_path, state_path); - - // Check if pad file exists - if (access(pad_path, R_OK) != 0) { - printf("Error: Pad file %s not found\n", pad_path); - free(pad_chksum); - return 1; - } - - // Read current offset - if (read_state_offset(pad_chksum, ¤t_offset) != 0) { - printf("Error: Cannot read state file\n"); - free(pad_chksum); - return 1; - } - - // Ensure we never encrypt before offset 32 (reserved for checksum encryption) - if (current_offset < 32) { - printf("Warning: State offset below reserved area, adjusting to 32\n"); - current_offset = 32; - if (write_state_offset(pad_chksum, current_offset) != 0) { - printf("Warning: Failed to update state file\n"); - } - } - - // Calculate XOR checksum of pad file - if (calculate_checksum(pad_path, chksum_hex) != 0) { - printf("Error: Cannot calculate pad checksum\n"); - free(pad_chksum); - return 1; - } - - // Get input text - either from parameter or user input - if (input_text != NULL) { - // Use provided text - strncpy(text_buffer, input_text, sizeof(text_buffer) - 1); - text_buffer[sizeof(text_buffer) - 1] = '\0'; - } else { - // Get input text from user (interactive mode) - if (is_interactive_mode) { - printf("\nText input options:\n"); - printf(" 1. Type text directly\n"); - printf(" 2. Use text editor\n"); - printf("Enter choice (1-2): "); - } - - char input_choice[10] = "1"; // Default to direct input in non-interactive mode - if (is_interactive_mode) { - if (!fgets(input_choice, sizeof(input_choice), stdin)) { - printf("Error: Failed to read input\n"); - free(pad_chksum); - return 1; - } - } - - if (is_interactive_mode && atoi(input_choice) == 2) { - // Use text editor - if (launch_text_editor(NULL, text_buffer, sizeof(text_buffer)) != 0) { - if (is_interactive_mode) { - printf("Falling back to direct text input.\n"); - printf("Enter text to encrypt: "); - } - fflush(stdout); - - if (fgets(text_buffer, sizeof(text_buffer), stdin) == NULL) { - printf("Error: Failed to read input\n"); - free(pad_chksum); - return 1; - } - - // Remove newline if present - size_t len = strlen(text_buffer); - if (len > 0 && text_buffer[len - 1] == '\n') { - text_buffer[len - 1] = '\0'; - } - } - } else { - // Direct text input - if (is_interactive_mode) { - printf("Enter text to encrypt: "); - fflush(stdout); - } - - if (fgets(text_buffer, sizeof(text_buffer), stdin) == NULL) { - printf("Error: Failed to read input\n"); - free(pad_chksum); - return 1; - } - - // Remove newline if present - size_t len = strlen(text_buffer); - if (len > 0 && text_buffer[len - 1] == '\n') { - text_buffer[len - 1] = '\0'; - } - } - } - - size_t input_len = strlen(text_buffer); - if (input_len == 0) { - printf("Error: No input provided\n"); - free(pad_chksum); - return 1; - } - - // Check if we have enough pad space - struct stat pad_stat; - if (stat(pad_path, &pad_stat) != 0) { - printf("Error: Cannot get pad file size\n"); - free(pad_chksum); - return 1; - } - - if (current_offset + input_len > (uint64_t)pad_stat.st_size) { - printf("Error: Not enough pad space remaining\n"); - printf("Need: %lu bytes, Available: %lu bytes\n", - input_len, (uint64_t)pad_stat.st_size - current_offset); - free(pad_chksum); - return 1; - } - - // Read pad data at current offset - FILE* pad_file = fopen(pad_path, "rb"); - if (!pad_file) { - printf("Error: Cannot open pad file\n"); - free(pad_chksum); - return 1; - } - - if (fseek(pad_file, current_offset, SEEK_SET) != 0) { - printf("Error: Cannot seek to offset in pad file\n"); - fclose(pad_file); - free(pad_chksum); - return 1; - } - - unsigned char* pad_data = malloc(input_len); - if (fread(pad_data, 1, input_len, pad_file) != input_len) { - printf("Error: Cannot read pad data\n"); - free(pad_data); - fclose(pad_file); - free(pad_chksum); - return 1; - } - fclose(pad_file); - - // Use universal XOR operation for encryption - unsigned char* ciphertext = malloc(input_len); - if (universal_xor_operation((const unsigned char*)text_buffer, input_len, pad_data, ciphertext) != 0) { - printf("Error: Encryption operation failed\n"); - free(pad_data); - free(ciphertext); - free(pad_chksum); - return 1; - } - - // Update state offset - if (write_state_offset(pad_chksum, current_offset + input_len) != 0) { - printf("Warning: Failed to update state file\n"); - } - - // Use universal ASCII armor generator - char* ascii_output; - if (generate_ascii_armor(chksum_hex, current_offset, ciphertext, input_len, &ascii_output) != 0) { - printf("Error: Failed to generate ASCII armor\n"); - free(pad_data); - free(ciphertext); - free(pad_chksum); - return 1; - } - - // Output with appropriate formatting - clean format for piping, spaced format for interactive - int is_interactive = (input_text == NULL); // Interactive if no input_text provided - - if (is_interactive) { - printf("\n\n\n%s\n\n", ascii_output); - } else { - printf("%s\n", ascii_output); // Add newline for proper piping with tee - } - - // Cleanup - free(pad_data); - free(ciphertext); - free(ascii_output); - free(pad_chksum); - - return 0; -} - -int decrypt_text(const char* pad_identifier, const char* encrypted_message) { - // Use universal decrypt function with mode based on global interactive mode detection - (void)pad_identifier; // Suppress unused parameter warning - chksum comes from message - decrypt_mode_t mode = is_interactive_mode ? DECRYPT_MODE_INTERACTIVE : DECRYPT_MODE_SILENT; - return universal_decrypt(encrypted_message, NULL, mode); -} - -int encrypt_file(const char* pad_identifier, const char* input_file, const char* output_file, int ascii_armor) { - char* pad_chksum = find_pad_by_prefix(pad_identifier); - if (!pad_chksum) { - return 1; - } - - char chksum_hex[MAX_HASH_LENGTH]; - uint64_t current_offset; - - char pad_path[MAX_HASH_LENGTH + 20]; - char state_path[MAX_HASH_LENGTH + 20]; - get_pad_path(pad_chksum, pad_path, state_path); - - // Check if input file exists and get its size - struct stat input_stat; - if (stat(input_file, &input_stat) != 0) { - printf("Error: Input file %s not found\n", input_file); - free(pad_chksum); - return 1; - } - - uint64_t file_size = input_stat.st_size; - if (file_size == 0) { - printf("Error: Input file is empty\n"); - free(pad_chksum); - return 1; - } - - // Check if pad file exists - if (access(pad_path, R_OK) != 0) { - printf("Error: Pad file %s not found\n", pad_path); - free(pad_chksum); - return 1; - } - - // Read current offset - if (read_state_offset(pad_chksum, ¤t_offset) != 0) { - printf("Error: Cannot read state file\n"); - free(pad_chksum); - return 1; - } - - // Ensure we never encrypt before offset 32 - if (current_offset < 32) { - printf("Warning: State offset below reserved area, adjusting to 32\n"); - current_offset = 32; - if (write_state_offset(pad_chksum, current_offset) != 0) { - printf("Warning: Failed to update state file\n"); - } - } - - // Calculate XOR checksum of pad file - if (calculate_checksum(pad_path, chksum_hex) != 0) { - printf("Error: Cannot calculate pad checksum\n"); - free(pad_chksum); - return 1; - } - - // Check if we have enough pad space - struct stat pad_stat; - if (stat(pad_path, &pad_stat) != 0) { - printf("Error: Cannot get pad file size\n"); - free(pad_chksum); - return 1; - } - - if (current_offset + file_size > (uint64_t)pad_stat.st_size) { - printf("Error: Not enough pad space remaining\n"); - printf("Need: %lu bytes, Available: %lu bytes\n", - file_size, (uint64_t)pad_stat.st_size - current_offset); - free(pad_chksum); - return 1; - } - - // Generate output filename if not specified, using files directory - char default_output[512]; - if (output_file == NULL) { - char temp_output[512]; - if (ascii_armor) { - snprintf(temp_output, sizeof(temp_output), "%s.otp.asc", input_file); - } else { - snprintf(temp_output, sizeof(temp_output), "%s.otp", input_file); - } - - // Apply files directory default path - get_default_file_path(temp_output, default_output, sizeof(default_output)); - output_file = default_output; - } - - // Open input file - FILE* input_fp = fopen(input_file, "rb"); - if (!input_fp) { - printf("Error: Cannot open input file %s\n", input_file); - free(pad_chksum); - return 1; - } - - // Open pad file - FILE* pad_file = fopen(pad_path, "rb"); - if (!pad_file) { - printf("Error: Cannot open pad file\n"); - fclose(input_fp); - free(pad_chksum); - return 1; - } - - if (fseek(pad_file, current_offset, SEEK_SET) != 0) { - printf("Error: Cannot seek to offset in pad file\n"); - fclose(input_fp); - fclose(pad_file); - free(pad_chksum); - return 1; - } - - // Read and encrypt file - unsigned char buffer[64 * 1024]; - unsigned char pad_buffer[64 * 1024]; - unsigned char* encrypted_data = malloc(file_size); - uint64_t bytes_processed = 0; - time_t start_time = time(NULL); - - printf("Encrypting %s...\n", input_file); - - while (bytes_processed < file_size) { - uint64_t chunk_size = sizeof(buffer); - if (file_size - bytes_processed < chunk_size) { - chunk_size = file_size - bytes_processed; - } - - // Read file data - if (fread(buffer, 1, chunk_size, input_fp) != chunk_size) { - printf("Error: Cannot read input file data\n"); - free(encrypted_data); - fclose(input_fp); - fclose(pad_file); - free(pad_chksum); - return 1; - } - - // Read pad data - if (fread(pad_buffer, 1, chunk_size, pad_file) != chunk_size) { - printf("Error: Cannot read pad data\n"); - free(encrypted_data); - fclose(input_fp); - fclose(pad_file); - free(pad_chksum); - return 1; - } - - // Use universal XOR operation for encryption - if (universal_xor_operation(buffer, chunk_size, pad_buffer, &encrypted_data[bytes_processed]) != 0) { - printf("Error: Encryption operation failed\n"); - free(encrypted_data); - fclose(input_fp); - fclose(pad_file); - free(pad_chksum); - return 1; - } - - bytes_processed += chunk_size; - - // Show progress for large files (> 10MB) - if (file_size > 10 * 1024 * 1024 && bytes_processed % (1024 * 1024) == 0) { - show_progress(bytes_processed, file_size, start_time); - } - } - - if (file_size > 10 * 1024 * 1024) { - show_progress(file_size, file_size, start_time); - printf("\n"); - } - - fclose(input_fp); - fclose(pad_file); - - // Write output file - if (ascii_armor) { - // ASCII armored format - same as message format - FILE* output_fp = fopen(output_file, "w"); - if (!output_fp) { - printf("Error: Cannot create output file %s\n", output_file); - free(encrypted_data); - free(pad_chksum); - return 1; - } - - // Use universal ASCII armor generator - char* ascii_output; - if (generate_ascii_armor(chksum_hex, current_offset, encrypted_data, file_size, &ascii_output) != 0) { - printf("Error: Failed to generate ASCII armor\n"); - fclose(output_fp); - free(encrypted_data); - free(pad_chksum); - return 1; - } - - // Write the ASCII armored output to file - fprintf(output_fp, "%s", ascii_output); - - fclose(output_fp); - free(ascii_output); - } else { - // Binary format - FILE* output_fp = fopen(output_file, "wb"); - if (!output_fp) { - printf("Error: Cannot create output file %s\n", output_file); - free(encrypted_data); - free(pad_chksum); - return 1; - } - - // Write binary header - // Magic: "OTP\0" - fwrite("OTP\0", 1, 4, output_fp); - - // Version: 2 bytes - uint16_t version = 1; - fwrite(&version, sizeof(uint16_t), 1, output_fp); - - // Pad checksum: 32 bytes (binary) - unsigned char pad_chksum_bin[32]; - for (int i = 0; i < 32; i++) { - sscanf(chksum_hex + i*2, "%2hhx", &pad_chksum_bin[i]); - } - fwrite(pad_chksum_bin, 1, 32, output_fp); - - // Pad offset: 8 bytes - fwrite(¤t_offset, sizeof(uint64_t), 1, output_fp); - - - // File mode: 4 bytes - uint32_t file_mode = input_stat.st_mode; - fwrite(&file_mode, sizeof(uint32_t), 1, output_fp); - - // File size: 8 bytes - fwrite(&file_size, sizeof(uint64_t), 1, output_fp); - - // Encrypted data - fwrite(encrypted_data, 1, file_size, output_fp); - - fclose(output_fp); - } - - // Update state offset - if (write_state_offset(pad_chksum, current_offset + file_size) != 0) { - printf("Warning: Failed to update state file\n"); - } - - printf("File encrypted successfully: %s\n", output_file); - if (ascii_armor) { - printf("Format: ASCII armored (.otp.asc)\n"); - } else { - printf("Format: Binary (.otp)\n"); - } - - // Pause before returning to menu to let user see the success message - print_centered_header("File Encryption Complete", 1); - - // Cleanup - free(encrypted_data); - free(pad_chksum); - - return 0; -} - -int decrypt_file(const char* input_file, const char* output_file) { - // Check if input file exists - if (access(input_file, R_OK) != 0) { - printf("Error: Input file %s not found\n", input_file); - return 1; - } - - FILE* input_fp = fopen(input_file, "rb"); - if (!input_fp) { - printf("Error: Cannot open input file %s\n", input_file); - return 1; - } - - // Read first few bytes to determine format - char magic[4]; - if (fread(magic, 1, 4, input_fp) != 4) { - printf("Error: Cannot read file header\n"); - fclose(input_fp); - return 1; - } - - fseek(input_fp, 0, SEEK_SET); // Reset to beginning - - if (memcmp(magic, "OTP\0", 4) == 0) { - // Binary format - return decrypt_binary_file(input_fp, output_file); - } else { - // Assume ASCII armored format, read entire file as text - fclose(input_fp); - return decrypt_ascii_file(input_file, output_file); - } -} - -int decrypt_binary_file(FILE* input_fp, const char* output_file) { - // Read binary header - char magic[4]; - uint16_t version; - unsigned char pad_chksum_bin[32]; - uint64_t pad_offset; - uint32_t file_mode; - uint64_t file_size; - - if (fread(magic, 1, 4, input_fp) != 4 || - fread(&version, sizeof(uint16_t), 1, input_fp) != 1 || - fread(pad_chksum_bin, 1, 32, input_fp) != 32 || - fread(&pad_offset, sizeof(uint64_t), 1, input_fp) != 1 || - fread(&file_mode, sizeof(uint32_t), 1, input_fp) != 1 || - fread(&file_size, sizeof(uint64_t), 1, input_fp) != 1) { - printf("Error: Cannot read binary header\n"); - fclose(input_fp); - return 1; - } - - if (memcmp(magic, "OTP\0", 4) != 0) { - printf("Error: Invalid binary format\n"); - fclose(input_fp); - return 1; - } - - // Convert binary checksum to hex - char pad_chksum_hex[65]; - for (int i = 0; i < 32; i++) { - sprintf(pad_chksum_hex + i*2, "%02x", pad_chksum_bin[i]); - } - pad_chksum_hex[64] = '\0'; - - printf("Decrypting binary file...\n"); - printf("File size: %lu bytes\n", file_size); - - // Check if we have the required pad - char pad_path[MAX_HASH_LENGTH + 20]; - char state_path[MAX_HASH_LENGTH + 20]; - get_pad_path(pad_chksum_hex, pad_path, state_path); - - if (access(pad_path, R_OK) != 0) { - printf("Error: Required pad not found: %s\n", pad_chksum_hex); - printf("Available pads:\n"); - char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0); - if (selected) { - free(selected); - } - fclose(input_fp); - return 1; - } - - // Determine output filename - char default_output[512]; - if (output_file == NULL) { - snprintf(default_output, sizeof(default_output), "decrypted.bin"); - output_file = default_output; - } - - // Read encrypted data - unsigned char* encrypted_data = malloc(file_size); - if (fread(encrypted_data, 1, file_size, input_fp) != file_size) { - printf("Error: Cannot read encrypted data\n"); - free(encrypted_data); - fclose(input_fp); - return 1; - } - fclose(input_fp); - - // Open pad file and decrypt - FILE* pad_file = fopen(pad_path, "rb"); - if (!pad_file) { - printf("Error: Cannot open pad file\n"); - free(encrypted_data); - return 1; - } - - if (fseek(pad_file, pad_offset, SEEK_SET) != 0) { - printf("Error: Cannot seek to offset in pad file\n"); - free(encrypted_data); - fclose(pad_file); - return 1; - } - - unsigned char* pad_data = malloc(file_size); - if (fread(pad_data, 1, file_size, pad_file) != file_size) { - printf("Error: Cannot read pad data\n"); - free(encrypted_data); - free(pad_data); - fclose(pad_file); - return 1; - } - fclose(pad_file); - - // Use universal XOR operation for decryption - if (universal_xor_operation(encrypted_data, file_size, pad_data, encrypted_data) != 0) { - printf("Error: Decryption operation failed\n"); - free(encrypted_data); - free(pad_data); - return 1; - } - - // Write decrypted file - FILE* output_fp = fopen(output_file, "wb"); - if (!output_fp) { - printf("Error: Cannot create output file %s\n", output_file); - free(encrypted_data); - free(pad_data); - return 1; - } - - if (fwrite(encrypted_data, 1, file_size, output_fp) != file_size) { - printf("Error: Cannot write decrypted data\n"); - free(encrypted_data); - free(pad_data); - fclose(output_fp); - return 1; - } - fclose(output_fp); - - // Restore file permissions - if (chmod(output_file, file_mode) != 0) { - printf("Warning: Cannot restore file permissions\n"); - } - - printf("File decrypted successfully: %s\n", output_file); - printf("Restored permissions and metadata\n"); - - // Pause before returning to menu to let user see the success message - print_centered_header("File Decryption Complete", 1); - - // Cleanup - free(encrypted_data); - free(pad_data); - - return 0; -} - -int decrypt_ascii_file(const char* input_file, const char* output_file) { - // Use universal decrypt function with file-to-file mode - return universal_decrypt(input_file, output_file, DECRYPT_MODE_FILE_TO_FILE); -} - -int read_state_offset(const char* pad_chksum, uint64_t* offset) { - char state_filename[1024]; - snprintf(state_filename, sizeof(state_filename), "%s/%s.state", current_pads_dir, pad_chksum); - - FILE* state_file = fopen(state_filename, "rb"); - if (!state_file) { - *offset = 0; - return 0; - } - - if (fread(offset, sizeof(uint64_t), 1, state_file) != 1) { - fclose(state_file); - *offset = 0; - return 0; - } - - fclose(state_file); - return 0; -} - -int write_state_offset(const char* pad_chksum, uint64_t offset) { - char state_filename[1024]; - snprintf(state_filename, sizeof(state_filename), "%s/%s.state", current_pads_dir, pad_chksum); - - FILE* state_file = fopen(state_filename, "wb"); - if (!state_file) { - return 1; - } - - if (fwrite(&offset, sizeof(uint64_t), 1, state_file) != 1) { - fclose(state_file); - return 1; - } - - fclose(state_file); - return 0; -} - - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// DIRECTORY MANAGEMENT FUNCTIONS -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -int ensure_pads_directory(void) { - struct stat st = {0}; - if (stat(current_pads_dir, &st) == -1) { - if (mkdir(current_pads_dir, 0755) != 0) { - return 1; - } - } - return 0; -} - -const char* get_files_directory(void) { - struct stat st = {0}; - if (stat(FILES_DIR, &st) == 0 && S_ISDIR(st.st_mode)) { - return FILES_DIR; - } - return "."; // Fall back to current directory -} - -void get_default_file_path(const char* filename, char* result_path, size_t result_size) { - const char* files_dir = get_files_directory(); - - // If filename already has a path (contains '/'), use it as-is - if (strchr(filename, '/') != NULL) { - strncpy(result_path, filename, result_size - 1); - result_path[result_size - 1] = '\0'; - return; - } - - // Otherwise, prepend the files directory - snprintf(result_path, result_size, "%s/%s", files_dir, filename); -} - -void get_directory_display(const char* file_path, char* result, size_t result_size) { - // Extract directory path from full file path - char dir_path[512]; - char* last_slash = strrchr(file_path, '/'); - - if (last_slash) { - size_t dir_len = last_slash - file_path; - if (dir_len >= sizeof(dir_path)) { - dir_len = sizeof(dir_path) - 1; - } - strncpy(dir_path, file_path, dir_len); - dir_path[dir_len] = '\0'; - } else { - // No directory separator, assume current directory - strcpy(dir_path, "."); - } - - // USB Drive Detection and Smart Shortening - char* home_dir = getenv("HOME"); - - // Check for USB/removable media mount patterns - if (strstr(dir_path, "/media/") || strstr(dir_path, "/run/media/") || strstr(dir_path, "/mnt/")) { - // Extract USB label/name - char* media_start = NULL; - if (strstr(dir_path, "/media/")) { - media_start = strstr(dir_path, "/media/"); - } else if (strstr(dir_path, "/run/media/")) { - media_start = strstr(dir_path, "/run/media/"); - } else if (strstr(dir_path, "/mnt/")) { - media_start = strstr(dir_path, "/mnt/"); - } - - if (media_start) { - // Find the USB label part - char* path_after_media = strchr(media_start + 1, '/'); - if (path_after_media) { - path_after_media++; // Skip the slash - - // For /media/user/LABEL pattern, skip the username to get to the drive label - if (strstr(media_start, "/media/")) { - char* next_slash = strchr(path_after_media, '/'); - if (next_slash) { - path_after_media = next_slash + 1; - } - } - // For /run/media/user/LABEL pattern, skip the username - else if (strstr(media_start, "/run/media/")) { - char* next_slash = strchr(path_after_media, '/'); - if (next_slash) { - path_after_media = next_slash + 1; - } - } - - // Extract just the USB label (up to next slash or end) - char* label_end = strchr(path_after_media, '/'); - char usb_label[32]; - if (label_end) { - size_t label_len = label_end - path_after_media; - if (label_len > sizeof(usb_label) - 1) label_len = sizeof(usb_label) - 1; - strncpy(usb_label, path_after_media, label_len); - usb_label[label_len] = '\0'; - } else { - // USB label is the last part - strncpy(usb_label, path_after_media, sizeof(usb_label) - 1); - usb_label[sizeof(usb_label) - 1] = '\0'; - } - - // Format with USB: prefix, limiting total length to fit in result - snprintf(result, result_size, "USB:%s", usb_label); - // Truncate if too long - if (strlen(result) > 11) { - result[11] = '\0'; - } - return; - } - } - } - - // Home directory shortening - if (home_dir && strncmp(dir_path, home_dir, strlen(home_dir)) == 0) { - if (dir_path[strlen(home_dir)] == '/' || dir_path[strlen(home_dir)] == '\0') { - // Replace home directory with ~ - char temp[512]; - snprintf(temp, sizeof(temp), "~%s", dir_path + strlen(home_dir)); - - // If result is too long, truncate intelligently - if (strlen(temp) > 11) { - // Show ~/...end_part - char* last_part = strrchr(temp, '/'); - if (last_part && strlen(last_part) < 8) { - snprintf(result, result_size, "~...%s", last_part); - } else { - strncpy(result, temp, 11); - result[11] = '\0'; - } - } else { - strncpy(result, temp, result_size - 1); - result[result_size - 1] = '\0'; - } - return; - } - } - - // Current working directory - if (strcmp(dir_path, ".") == 0 || strcmp(dir_path, current_pads_dir) == 0) { - strncpy(result, "pads", result_size - 1); - result[result_size - 1] = '\0'; - return; - } - - // System/other paths - smart truncation with ellipsis - if (strlen(dir_path) > 11) { - // Try to show the most meaningful part - char* last_part = strrchr(dir_path, '/'); - if (last_part && strlen(last_part) < 9) { - // Show .../last_part - snprintf(result, result_size, "...%s", last_part); - } else { - // Show first part with ellipsis - strncpy(result, dir_path, 8); - strncpy(result + 8, "...", result_size - 8 - 1); - result[result_size - 1] = '\0'; - } - } else { - // Short enough, use as-is - strncpy(result, dir_path, result_size - 1); - result[result_size - 1] = '\0'; - } -} - -void get_pad_path(const char* chksum, char* pad_path, char* state_path) { - snprintf(pad_path, 1024, "%s/%s.pad", current_pads_dir, chksum); - snprintf(state_path, 1024, "%s/%s.state", current_pads_dir, chksum); -} - -// Stdin detection functions implementation -int has_stdin_data(void) { - // Check if stdin is a pipe/redirect (not a terminal) - if (!isatty(STDIN_FILENO)) { - return 1; - } - return 0; -} - -char* read_stdin_text(void) { - size_t capacity = 4096; - size_t length = 0; - char* buffer = malloc(capacity); - - if (!buffer) { - return NULL; - } - - char chunk[1024]; - while (fgets(chunk, sizeof(chunk), stdin)) { - size_t chunk_len = strlen(chunk); - - // Ensure we have enough capacity - while (length + chunk_len >= capacity) { - capacity *= 2; - char* new_buffer = realloc(buffer, capacity); - if (!new_buffer) { - free(buffer); - return NULL; - } - buffer = new_buffer; - } - - strcpy(buffer + length, chunk); - length += chunk_len; - } - - // Remove trailing newline if present - if (length > 0 && buffer[length - 1] == '\n') { - buffer[length - 1] = '\0'; - length--; - } - - // If empty, free and return NULL - if (length == 0) { - free(buffer); - return NULL; - } - - return buffer; -} - -int pipe_mode(int argc, char* argv[], const char* piped_text) { - (void)argc; // Suppress unused parameter warning - (void)argv; // Suppress unused parameter warning - - // Check if we have a default pad configured - char* default_pad = get_default_pad_path(); - if (default_pad) { - // Verify the default pad exists and extract checksum - if (access(default_pad, R_OK) == 0) { - // Extract checksum from pad filename - char* filename = strrchr(default_pad, '/'); - if (!filename) filename = default_pad; - else filename++; // Skip the '/' - - // Extract checksum (remove .pad extension) - if (strlen(filename) >= 68 && strstr(filename, ".pad")) { - char pad_checksum[65]; - strncpy(pad_checksum, filename, 64); - pad_checksum[64] = '\0'; - - free(default_pad); - - // Encrypt using the default pad (silent mode) - return encrypt_text(pad_checksum, piped_text); - } - } - - fprintf(stderr, "Error: Default pad not found or invalid: %s\n", default_pad); - free(default_pad); - return 1; - } - - fprintf(stderr, "Error: No default pad configured for pipe mode\n"); - fprintf(stderr, "Configure a default pad in ~/.otp/otp.conf\n"); - return 1; -} - -// Preferences management functions implementation -int load_preferences(void) { - char* home_dir = getenv("HOME"); - if (!home_dir) { - return 1; // No home directory - } - - char preferences_dir[1024]; - char preferences_file[2048]; // Increased buffer size to accommodate longer paths - snprintf(preferences_dir, sizeof(preferences_dir), "%s/.otp", home_dir); - snprintf(preferences_file, sizeof(preferences_file), "%s/otp.conf", preferences_dir); - - FILE* file = fopen(preferences_file, "r"); - if (!file) { - // No preferences file exists - create it and set first pad as default - - // Create .otp directory if it doesn't exist - struct stat st = {0}; - if (stat(preferences_dir, &st) == -1) { - if (mkdir(preferences_dir, 0755) != 0) { - return 1; - } - } - - // Find the first available pad to set as default - DIR* dir = opendir(current_pads_dir); - if (dir) { - struct dirent* entry; - char first_pad_path[1024]; - int found_pad = 0; - - while ((entry = readdir(dir)) != NULL && !found_pad) { - if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) { - // Found a pad file - construct full absolute path - if (current_pads_dir[0] == '/') { - // Already absolute path - int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s", current_pads_dir, entry->d_name); - if (ret >= (int)sizeof(first_pad_path)) { - // Path was truncated, skip this entry - continue; - } - } else { - // Relative path - make it absolute - char current_dir[512]; - if (getcwd(current_dir, sizeof(current_dir))) { - int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s/%s", current_dir, current_pads_dir, entry->d_name); - if (ret >= (int)sizeof(first_pad_path)) { - // Path was truncated, skip this entry - continue; - } - } else { - // Fallback to relative path - int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s", current_pads_dir, entry->d_name); - if (ret >= (int)sizeof(first_pad_path)) { - // Path was truncated, skip this entry - continue; - } - } - } - strncpy(default_pad_path, first_pad_path, sizeof(default_pad_path) - 1); - default_pad_path[sizeof(default_pad_path) - 1] = '\0'; - found_pad = 1; - } - } - closedir(dir); - - // Create the preferences file with the default pad - if (found_pad) { - save_preferences(); - } - } - - return 0; // Successfully initialized - } - - char line[1024]; - while (fgets(line, sizeof(line), file)) { - // Remove newline - line[strcspn(line, "\n")] = 0; - - // Skip empty lines and comments - if (strlen(line) == 0 || line[0] == '#') { - continue; - } - - // Parse key=value pairs - char* equals = strchr(line, '='); - if (equals) { - *equals = '\0'; - char* key = line; - char* value = equals + 1; - - // Trim whitespace - while (*key == ' ' || *key == '\t') key++; - while (*value == ' ' || *value == '\t') value++; - - if (strcmp(key, "default_pad") == 0) { - strncpy(default_pad_path, value, sizeof(default_pad_path) - 1); - default_pad_path[sizeof(default_pad_path) - 1] = '\0'; - } - } - } - - fclose(file); - return 0; -} - -int save_preferences(void) { - char* home_dir = getenv("HOME"); - if (!home_dir) { - return 1; - } - - char preferences_dir[1024]; - char preferences_file[2048]; // Increased buffer size to accommodate longer paths - snprintf(preferences_dir, sizeof(preferences_dir), "%s/.otp", home_dir); - snprintf(preferences_file, sizeof(preferences_file), "%s/otp.conf", preferences_dir); - - // Create .otp directory if it doesn't exist - struct stat st = {0}; - if (stat(preferences_dir, &st) == -1) { - if (mkdir(preferences_dir, 0755) != 0) { - return 1; - } - } - - FILE* file = fopen(preferences_file, "w"); - if (!file) { - return 1; - } - - fprintf(file, "# OTP Preferences File\n"); - fprintf(file, "# This file is automatically generated and updated by the OTP program\n\n"); - - if (strlen(default_pad_path) > 0) { - fprintf(file, "default_pad=%s\n", default_pad_path); - } - - fclose(file); - return 0; -} - -char* get_preference(const char* key) { - if (strcmp(key, "default_pad") == 0) { - if (strlen(default_pad_path) > 0) { - return strdup(default_pad_path); - } - } - return NULL; -} - -int set_preference(const char* key, const char* value) { - if (strcmp(key, "default_pad") == 0) { - if (value) { - strncpy(default_pad_path, value, sizeof(default_pad_path) - 1); - default_pad_path[sizeof(default_pad_path) - 1] = '\0'; - } else { - default_pad_path[0] = '\0'; - } - return save_preferences(); - } - return 1; -} - -char* get_default_pad_path(void) { - if (strlen(default_pad_path) > 0) { - return strdup(default_pad_path); - } - return NULL; -} - -int set_default_pad_path(const char* pad_path) { - if (!pad_path) { - return set_preference("default_pad", NULL); - } - - // Ensure we store the full absolute path - char absolute_path[1024]; - if (pad_path[0] == '/') { - // Already absolute path - strncpy(absolute_path, pad_path, sizeof(absolute_path) - 1); - absolute_path[sizeof(absolute_path) - 1] = '\0'; - } else { - // Relative path - make it absolute - char current_dir[512]; - if (getcwd(current_dir, sizeof(current_dir))) { - snprintf(absolute_path, sizeof(absolute_path), "%s/%s", current_dir, pad_path); - } else { - // Fallback to using the path as-is if getcwd fails - strncpy(absolute_path, pad_path, sizeof(absolute_path) - 1); - absolute_path[sizeof(absolute_path) - 1] = '\0'; - } - } - - return set_preference("default_pad", absolute_path); -} - -// OTP thumb drive detection function implementation -int detect_otp_thumb_drive(char* otp_drive_path, size_t path_size) { - const char* mount_dirs[] = {"/media", "/run/media", "/mnt", NULL}; - - for (int mount_idx = 0; mount_dirs[mount_idx] != NULL; mount_idx++) { - DIR* mount_dir = opendir(mount_dirs[mount_idx]); - if (!mount_dir) continue; - - struct dirent* mount_entry; - while ((mount_entry = readdir(mount_dir)) != NULL) { - if (mount_entry->d_name[0] == '.') continue; - - char mount_path[1024]; // Increased buffer size - snprintf(mount_path, sizeof(mount_path), "%s/%s", mount_dirs[mount_idx], mount_entry->d_name); - - // For /media, we need to go one level deeper (user directories) - if (strcmp(mount_dirs[mount_idx], "/media") == 0) { - // This is /media/[username] - look inside for drives - DIR* user_dir = opendir(mount_path); - if (!user_dir) continue; - - struct dirent* user_entry; - while ((user_entry = readdir(user_dir)) != NULL) { - if (user_entry->d_name[0] == '.') continue; - - // Check if drive name starts with "OTP" - if (strncmp(user_entry->d_name, "OTP", 3) != 0) continue; - - char user_mount_path[2048]; // Increased buffer size - // Verify buffer has enough space before concatenation - size_t mount_len = strlen(mount_path); - size_t entry_len = strlen(user_entry->d_name); - if (mount_len + entry_len + 2 < sizeof(user_mount_path)) { - snprintf(user_mount_path, sizeof(user_mount_path), "%s/%s", mount_path, user_entry->d_name); - - // Check if this is a readable directory - DIR* drive_dir = opendir(user_mount_path); - if (drive_dir) { - closedir(drive_dir); - strncpy(otp_drive_path, user_mount_path, path_size - 1); - otp_drive_path[path_size - 1] = '\0'; - closedir(user_dir); - closedir(mount_dir); - return 1; // Found OTP drive - } - } - } - closedir(user_dir); - } else if (strcmp(mount_dirs[mount_idx], "/run/media") == 0) { - // For /run/media, we need to go one level deeper (skip username) - DIR* user_dir = opendir(mount_path); - if (!user_dir) continue; - - struct dirent* user_entry; - while ((user_entry = readdir(user_dir)) != NULL) { - if (user_entry->d_name[0] == '.') continue; - - // Check if drive name starts with "OTP" - if (strncmp(user_entry->d_name, "OTP", 3) != 0) continue; - - char user_mount_path[2048]; // Increased buffer size - snprintf(user_mount_path, sizeof(user_mount_path), "%s/%s", mount_path, user_entry->d_name); - - // Check if this is a readable directory - DIR* drive_dir = opendir(user_mount_path); - if (drive_dir) { - closedir(drive_dir); - strncpy(otp_drive_path, user_mount_path, path_size - 1); - otp_drive_path[path_size - 1] = '\0'; - closedir(user_dir); - closedir(mount_dir); - return 1; // Found OTP drive - } - } - closedir(user_dir); - } else { - // Direct mount point (like /mnt/OTP_DRIVE) - // Check if drive name starts with "OTP" - if (strncmp(mount_entry->d_name, "OTP", 3) == 0) { - DIR* drive_dir = opendir(mount_path); - if (drive_dir) { - closedir(drive_dir); - strncpy(otp_drive_path, mount_path, path_size - 1); - otp_drive_path[path_size - 1] = '\0'; - closedir(mount_dir); - return 1; // Found OTP drive - } - } - } - } - closedir(mount_dir); - } - - return 0; // No OTP drive found -} - - - - -// Custom base64 encode function - -char* custom_base64_encode(const unsigned char* input, int length) { - int output_length = 4 * ((length + 2) / 3); - char* encoded = malloc(output_length + 1); - if (!encoded) return NULL; - - int i, j; - for (i = 0, j = 0; i < length;) { - uint32_t octet_a = i < length ? input[i++] : 0; - uint32_t octet_b = i < length ? input[i++] : 0; - uint32_t octet_c = i < length ? input[i++] : 0; - - uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c; - - encoded[j++] = base64_chars[(triple >> 18) & 63]; - encoded[j++] = base64_chars[(triple >> 12) & 63]; - encoded[j++] = base64_chars[(triple >> 6) & 63]; - encoded[j++] = base64_chars[triple & 63]; - } - - // Add padding - for (int pad = 0; pad < (3 - length % 3) % 3; pad++) { - encoded[output_length - 1 - pad] = '='; - } - - encoded[output_length] = '\0'; - return encoded; -} - -// Custom base64 decode function -unsigned char* custom_base64_decode(const char* input, int* output_length) { - int input_length = strlen(input); - if (input_length % 4 != 0) return NULL; - - *output_length = input_length / 4 * 3; - if (input[input_length - 1] == '=') (*output_length)--; - if (input[input_length - 2] == '=') (*output_length)--; - - unsigned char* decoded = malloc(*output_length); - if (!decoded) return NULL; - - int i, j; - for (i = 0, j = 0; i < input_length;) { - int sextet_a = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; - int sextet_b = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; - int sextet_c = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; - int sextet_d = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; - - if (sextet_a == -1 || sextet_b == -1 || sextet_c == -1 || sextet_d == -1) { - free(decoded); - return NULL; - } - - uint32_t triple = (sextet_a << 18) + (sextet_b << 12) + (sextet_c << 6) + sextet_d; - - if (j < *output_length) decoded[j++] = (triple >> 16) & 255; - if (j < *output_length) decoded[j++] = (triple >> 8) & 255; - if (j < *output_length) decoded[j++] = triple & 255; - } - - return decoded; -} - - - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// ADD ENTROPY -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// TRUERNG DEVICE DETECTION AND COMMUNICATION -// Ported from true_rng/main.c for entropy collection -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -// Read USB device info from sysfs (ported from TrueRNG reference) -int read_usb_device_info(const char* port_name, char* vid, char* pid) { - char path[512]; - FILE *fp; - - // Try to read idVendor first (works for both ttyUSB and ttyACM devices) - snprintf(path, sizeof(path), "/sys/class/tty/%s/device/../idVendor", port_name); - fp = fopen(path, "r"); - if (fp) { - if (fgets(vid, 8, fp) != NULL) { - // Remove newline if present - int len = strlen(vid); - if (len > 0 && vid[len-1] == '\n') { - vid[len-1] = '\0'; - } - } else { - fclose(fp); - return 0; - } - fclose(fp); - } else { - return 0; - } - - // Try to read idProduct - snprintf(path, sizeof(path), "/sys/class/tty/%s/device/../idProduct", port_name); - fp = fopen(path, "r"); - if (fp) { - if (fgets(pid, 8, fp) != NULL) { - // Remove newline if present - int len = strlen(pid); - if (len > 0 && pid[len-1] == '\n') { - pid[len-1] = '\0'; - } - } else { - fclose(fp); - return 0; - } - fclose(fp); - return 1; - } else { - return 0; - } -} - -// Find TrueRNG device port (ported and adapted from TrueRNG reference) -// Returns: 0=not found, 1=TrueRNGproV2, 2=TrueRNGpro, 3=TrueRNG -int find_truerng_port(char* port_path, size_t port_path_size, truerng_device_type_t* device_type) { - DIR *dir; - struct dirent *entry; - char vid[8], pid[8]; - int device_found = 0; - - dir = opendir("/dev"); - if (dir == NULL) { - return 0; - } - - while ((entry = readdir(dir)) != NULL) { - // Look for ttyUSB* or ttyACM* devices - if (strncmp(entry->d_name, "ttyUSB", 6) == 0 || - strncmp(entry->d_name, "ttyACM", 6) == 0) { - - if (read_usb_device_info(entry->d_name, vid, pid)) { - // Convert to uppercase for comparison - for (int i = 0; vid[i]; i++) vid[i] = toupper(vid[i]); - for (int i = 0; pid[i]; i++) pid[i] = toupper(pid[i]); - - // Check for TrueRNGproV2 - if (strcmp(vid, TRUERNGPROV2_VID) == 0 && strcmp(pid, TRUERNGPROV2_PID) == 0) { - snprintf(port_path, port_path_size, "/dev/%s", entry->d_name); - *device_type = TRUERNG_PRO_V2; - device_found = 1; - break; - } - - // Check for TrueRNGpro - if (strcmp(vid, TRUERNGPRO_VID) == 0 && strcmp(pid, TRUERNGPRO_PID) == 0) { - snprintf(port_path, port_path_size, "/dev/%s", entry->d_name); - *device_type = TRUERNG_PRO; - device_found = 2; - break; - } - - // Check for TrueRNG - if (strcmp(vid, TRUERNG_VID) == 0 && strcmp(pid, TRUERNG_PID) == 0) { - snprintf(port_path, port_path_size, "/dev/%s", entry->d_name); - *device_type = TRUERNG_ORIGINAL; - device_found = 3; - break; - } - } - } - } - - closedir(dir); - return device_found; -} - -// Setup serial port for TrueRNG communication (ported from TrueRNG reference) -int setup_truerng_serial_port(const char* port_path) { - int fd; - struct termios tty; - - fd = open(port_path, O_RDWR | O_NOCTTY); - if (fd < 0) { - return -1; - } - - // Get current port settings - if (tcgetattr(fd, &tty) != 0) { - close(fd); - return -1; - } - - // Set baud rate (TrueRNG devices use 9600) - cfsetospeed(&tty, B9600); - cfsetispeed(&tty, B9600); - - // 8N1 mode - tty.c_cflag &= ~PARENB; // No parity - tty.c_cflag &= ~CSTOPB; // 1 stop bit - tty.c_cflag &= ~CSIZE; // Clear size bits - tty.c_cflag |= CS8; // 8 data bits - tty.c_cflag &= ~CRTSCTS; // No hardware flow control - tty.c_cflag |= CREAD | CLOCAL; // Enable reading and ignore modem controls - - // Raw input mode - tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); - tty.c_iflag &= ~(IXON | IXOFF | IXANY); - tty.c_oflag &= ~OPOST; - - // Set for blocking reads - wait for data indefinitely - tty.c_cc[VMIN] = 1; // Block until at least 1 character is received - tty.c_cc[VTIME] = 0; // No timeout - - // Apply settings - if (tcsetattr(fd, TCSANOW, &tty) != 0) { - close(fd); - return -1; - } - - // Flush input buffer - tcflush(fd, TCIFLUSH); - - // Set DTR - int status; - ioctl(fd, TIOCMGET, &status); - status |= TIOCM_DTR; - ioctl(fd, TIOCMSET, &status); - - return fd; -} - -// Get friendly name for TrueRNG device type -const char* get_truerng_device_name(truerng_device_type_t device_type) { - switch (device_type) { - case TRUERNG_PRO_V2: return "TrueRNGproV2"; - case TRUERNG_PRO: return "TrueRNGpro"; - case TRUERNG_ORIGINAL: return "TrueRNG"; - default: return "Unknown"; - } -} - -// Collect entropy from TrueRNG device with equivalent quality to keyboard entropy -int collect_truerng_entropy(unsigned char* entropy_buffer, size_t target_bytes, - size_t* collected_bytes, int display_progress) { - char port_path[512]; - truerng_device_type_t device_type; - int serial_fd = -1; - - // Find TrueRNG device - if (!find_truerng_port(port_path, sizeof(port_path), &device_type)) { - if (display_progress) { - printf("No TrueRNG device found.\n"); - printf("\nSupported devices:\n"); - printf(" - TrueRNG (PID: %s, VID: %s)\n", TRUERNG_VID, TRUERNG_PID); - printf(" - TrueRNGpro (PID: %s, VID: %s)\n", TRUERNGPRO_VID, TRUERNGPRO_PID); - printf(" - TrueRNGproV2 (PID: %s, VID: %s)\n", TRUERNGPROV2_VID, TRUERNGPROV2_PID); - printf("\nPlease connect a TrueRNG device and try again.\n"); - } - return 1; // Device not found - } - - if (display_progress) { - printf("Found %s at %s\n", get_truerng_device_name(device_type), port_path); - printf("Collecting %zu bytes of entropy...\n", target_bytes); - } - - // Setup serial port - serial_fd = setup_truerng_serial_port(port_path); - if (serial_fd < 0) { - if (display_progress) { - printf("Error: Cannot open TrueRNG device at %s\n", port_path); - printf("Check device permissions or run as root.\n"); - } - return 2; // Serial port setup failed - } - - // Collect entropy data - size_t bytes_read = 0; - unsigned char buffer[1024]; // Read in 1KB chunks - time_t start_time = time(NULL); - - while (bytes_read < target_bytes) { - size_t chunk_size = sizeof(buffer); - if (target_bytes - bytes_read < chunk_size) { - chunk_size = target_bytes - bytes_read; - } - - size_t bytes_in_chunk = 0; - while (bytes_in_chunk < chunk_size) { - ssize_t result = read(serial_fd, buffer + bytes_in_chunk, chunk_size - bytes_in_chunk); - if (result < 0) { - if (display_progress) { - printf("Error: Failed to read from TrueRNG device\n"); - } - close(serial_fd); - return 3; // Read failed - } else if (result == 0) { - if (display_progress) { - printf("Error: No data received from TrueRNG device\n"); - } - close(serial_fd); - return 4; // No data - } - bytes_in_chunk += result; - } - - // Copy to entropy buffer - memcpy(entropy_buffer + bytes_read, buffer, chunk_size); - bytes_read += chunk_size; - - // Show progress for large collections - if (display_progress && bytes_read % (4 * 1024) == 0) { // Every 4KB - double percentage = (double)bytes_read / target_bytes * 100.0; - printf("Progress: %.1f%% (%zu/%zu bytes)\r", percentage, bytes_read, target_bytes); - fflush(stdout); - } - } - - close(serial_fd); - - if (display_progress) { - double collection_time = difftime(time(NULL), start_time); - printf("\nāœ“ TrueRNG entropy collection complete!\n"); - printf(" Collected: %zu bytes in %.1f seconds\n", bytes_read, collection_time); - printf(" Device: %s\n", get_truerng_device_name(device_type)); - if (collection_time > 0) { - printf(" Rate: %.1f KB/s\n", (double)bytes_read / collection_time / 1024.0); - } - } - - *collected_bytes = bytes_read; - return 0; // Success -} - -// Helper function to format time in human-readable format -void format_time_remaining(double seconds, char* buffer, size_t buffer_size) { - if (seconds < 60) { - snprintf(buffer, buffer_size, "%.0fs", seconds); - } else if (seconds < 3600) { - int mins = (int)(seconds / 60); - int secs = (int)(seconds) % 60; - snprintf(buffer, buffer_size, "%dm %02ds", mins, secs); - } else { - int hours = (int)(seconds / 3600); - int mins = (int)(seconds / 60) % 60; - snprintf(buffer, buffer_size, "%dh %02dm", hours, mins); - } -} - -// Streaming TrueRNG entropy collection for full pad enhancement -// This function applies entropy directly to the pad in chunks to avoid memory issues -int collect_truerng_entropy_streaming(const char* pad_chksum, size_t total_bytes, int display_progress) { - char port_path[512]; - truerng_device_type_t device_type; - int serial_fd = -1; - - // Find TrueRNG device - if (!find_truerng_port(port_path, sizeof(port_path), &device_type)) { - if (display_progress) { - printf("No TrueRNG device found.\n"); - printf("\nSupported devices:\n"); - printf(" - TrueRNG (PID: %s, VID: %s)\n", TRUERNG_VID, TRUERNG_PID); - printf(" - TrueRNGpro (PID: %s, VID: %s)\n", TRUERNGPRO_VID, TRUERNGPRO_PID); - printf(" - TrueRNGproV2 (PID: %s, VID: %s)\n", TRUERNGPROV2_VID, TRUERNGPROV2_PID); - printf("\nPlease connect a TrueRNG device and try again.\n"); - } - return 1; // Device not found - } - - if (display_progress) { - printf("Found %s at %s\n", get_truerng_device_name(device_type), port_path); - printf("Streaming %zu bytes of entropy directly to pad...\n", total_bytes); - } - - // Setup serial port - serial_fd = setup_truerng_serial_port(port_path); - if (serial_fd < 0) { - if (display_progress) { - printf("Error: Cannot open TrueRNG device at %s\n", port_path); - printf("Check device permissions or run as root.\n"); - } - return 2; // Serial port setup failed - } - - // For large pads (>10MB), do a 1MB test to estimate completion time - double estimated_rate = 0.0; - int user_confirmed = 1; // Default to confirmed for small pads - - if (total_bytes > 10 * 1024 * 1024 && display_progress) { - printf("\nLarge pad detected (%.1f MB). Running 1MB speed test...\n", - (double)total_bytes / (1024.0 * 1024.0)); - - // Test with 1MB sample - size_t test_bytes = 1024 * 1024; // 1MB - unsigned char* test_buffer = malloc(test_bytes); - if (!test_buffer) { - printf("Error: Cannot allocate test buffer\n"); - close(serial_fd); - return 3; - } - - time_t test_start = time(NULL); - size_t test_collected = 0; - - while (test_collected < test_bytes) { - size_t bytes_needed = test_bytes - test_collected; - size_t read_size = (bytes_needed > 1024) ? 1024 : bytes_needed; - - ssize_t result = read(serial_fd, test_buffer + test_collected, read_size); - if (result <= 0) { - printf("Error: TrueRNG test failed\n"); - free(test_buffer); - close(serial_fd); - return 4; - } - test_collected += result; - - // Show test progress - double test_progress = (double)test_collected / test_bytes * 100.0; - printf("\rSpeed test: %.1f%% complete", test_progress); - fflush(stdout); - } - - double test_time = difftime(time(NULL), test_start); - estimated_rate = (double)test_bytes / test_time; // bytes per second - - // Calculate estimated total time - double estimated_total_time = (double)total_bytes / estimated_rate; - - char time_str[64]; - format_time_remaining(estimated_total_time, time_str, sizeof(time_str)); - - printf("\nSpeed test complete: %.1f KB/s\n", estimated_rate / 1024.0); - printf("Estimated completion time: %s\n", time_str); - printf("\nProceed with full pad enhancement? (y/N): "); - fflush(stdout); - - char response[10]; - if (fgets(response, sizeof(response), stdin) == NULL || - (response[0] != 'y' && response[0] != 'Y')) { - printf("Operation cancelled by user.\n"); - free(test_buffer); - close(serial_fd); - return 5; // User cancelled - } - - user_confirmed = 1; - free(test_buffer); - - // Reset serial port for main operation - close(serial_fd); - serial_fd = setup_truerng_serial_port(port_path); - if (serial_fd < 0) { - printf("Error: Cannot reopen TrueRNG device\n"); - return 6; - } - } - - if (!user_confirmed) { - close(serial_fd); - return 5; // User cancelled - } - - // Get pad paths - char pad_path[1024]; - char state_path[1024]; - get_pad_path(pad_chksum, pad_path, state_path); - - // Open pad file for read/write with temporary permissions - if (chmod(pad_path, S_IRUSR | S_IWUSR) != 0) { - if (display_progress) { - printf("Error: Cannot change pad file permissions\n"); - } - close(serial_fd); - return 3; - } - - FILE* pad_file = fopen(pad_path, "r+b"); - if (!pad_file) { - if (display_progress) { - printf("Error: Cannot open pad file for modification: %s\n", pad_path); - } - chmod(pad_path, S_IRUSR); // Restore read-only - close(serial_fd); - return 4; - } - - // Process pad in chunks - const size_t chunk_size = 1024 * 1024; // 1MB chunks - unsigned char* entropy_chunk = malloc(chunk_size); - unsigned char* pad_chunk = malloc(chunk_size); - unsigned char* mixed_chunk = malloc(chunk_size); - - if (!entropy_chunk || !pad_chunk || !mixed_chunk) { - if (display_progress) { - printf("Error: Cannot allocate chunk buffers\n"); - } - free(entropy_chunk); - free(pad_chunk); - free(mixed_chunk); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - close(serial_fd); - return 5; - } - - size_t bytes_processed = 0; - time_t start_time = time(NULL); - - while (bytes_processed < total_bytes) { - size_t current_chunk_size = chunk_size; - if (total_bytes - bytes_processed < chunk_size) { - current_chunk_size = total_bytes - bytes_processed; - } - - // Collect entropy from TrueRNG for this chunk - size_t entropy_bytes_in_chunk = 0; - while (entropy_bytes_in_chunk < current_chunk_size) { - size_t bytes_needed = current_chunk_size - entropy_bytes_in_chunk; - size_t read_size = (bytes_needed > 1024) ? 1024 : bytes_needed; - - ssize_t result = read(serial_fd, entropy_chunk + entropy_bytes_in_chunk, read_size); - if (result < 0) { - if (display_progress) { - printf("Error: Failed to read from TrueRNG device\n"); - } - free(entropy_chunk); - free(pad_chunk); - free(mixed_chunk); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - close(serial_fd); - return 6; - } else if (result == 0) { - if (display_progress) { - printf("Error: No data received from TrueRNG device\n"); - } - free(entropy_chunk); - free(pad_chunk); - free(mixed_chunk); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - close(serial_fd); - return 7; - } - entropy_bytes_in_chunk += result; - } - - // Read current pad data for this chunk - if (fseek(pad_file, bytes_processed, SEEK_SET) != 0) { - if (display_progress) { - printf("Error: Cannot seek to offset %zu in pad file\n", bytes_processed); - } - free(entropy_chunk); - free(pad_chunk); - free(mixed_chunk); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - close(serial_fd); - return 8; - } - - if (fread(pad_chunk, 1, current_chunk_size, pad_file) != current_chunk_size) { - if (display_progress) { - printf("Error: Cannot read pad data at offset %zu\n", bytes_processed); - } - free(entropy_chunk); - free(pad_chunk); - free(mixed_chunk); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - close(serial_fd); - return 9; - } - - // Derive Chacha20 key and nonce from entropy chunk - unsigned char key[32], nonce[12]; - if (derive_chacha20_params(entropy_chunk, current_chunk_size, key, nonce) != 0) { - if (display_progress) { - printf("Error: Failed to derive Chacha20 parameters from entropy\n"); - } - free(entropy_chunk); - free(pad_chunk); - free(mixed_chunk); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - close(serial_fd); - return 10; - } - - // Generate keystream and XOR with pad data - uint32_t counter = bytes_processed / 64; // Chacha20 block counter - if (chacha20_encrypt(key, counter, nonce, pad_chunk, mixed_chunk, current_chunk_size) != 0) { - if (display_progress) { - printf("Error: Chacha20 keystream generation failed\n"); - } - free(entropy_chunk); - free(pad_chunk); - free(mixed_chunk); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - close(serial_fd); - return 11; - } - - // XOR existing pad with keystream (adds entropy) - for (size_t i = 0; i < current_chunk_size; i++) { - mixed_chunk[i] = pad_chunk[i] ^ mixed_chunk[i]; - } - - // Write modified data back to pad - if (fseek(pad_file, bytes_processed, SEEK_SET) != 0) { - if (display_progress) { - printf("Error: Cannot seek to write offset %zu\n", bytes_processed); - } - free(entropy_chunk); - free(pad_chunk); - free(mixed_chunk); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - close(serial_fd); - return 12; - } - - if (fwrite(mixed_chunk, 1, current_chunk_size, pad_file) != current_chunk_size) { - if (display_progress) { - printf("Error: Cannot write modified pad data\n"); - } - free(entropy_chunk); - free(pad_chunk); - free(mixed_chunk); - fclose(pad_file); - chmod(pad_path, S_IRUSR); - close(serial_fd); - return 13; - } - - bytes_processed += current_chunk_size; - - // Show progress and entropy samples for visual verification - if (display_progress) { - // Update progress more frequently for better user experience - always show cumulative amount - if (bytes_processed % (4 * 1024 * 1024) == 0 || bytes_processed == total_bytes) { // Every 4MB or at completion - double percentage = (double)bytes_processed / total_bytes * 100.0; - double elapsed = difftime(time(NULL), start_time); - double rate = 0.0; - if (elapsed > 0) { - rate = (double)bytes_processed / elapsed / (1024.0 * 1024.0); - } - - // Calculate estimated time remaining - char eta_str[64] = ""; - if (rate > 0.0 && bytes_processed < total_bytes) { - double remaining_bytes = (double)(total_bytes - bytes_processed); - double eta_seconds = remaining_bytes / (rate * 1024.0 * 1024.0); // Convert rate back to bytes/sec - format_time_remaining(eta_seconds, eta_str, sizeof(eta_str)); - } - - // Clear previous line and show progress bar with cumulative TrueRNG data generated - printf("\r\033[K"); // Clear line - printf("["); - int bar_width = 30; - int filled = (int)(percentage / 100.0 * bar_width); - for (int i = 0; i < filled; i++) printf("ā–ˆ"); - for (int i = filled; i < bar_width; i++) printf("ā–‘"); - - if (strlen(eta_str) > 0) { - printf("] %.1f%% - TrueRNG: %zu MB / %zu MB (%.1f MB/s) ETA: %s", - percentage, bytes_processed / (1024*1024), total_bytes / (1024*1024), rate, eta_str); - } else { - printf("] %.1f%% - TrueRNG: %zu MB / %zu MB (%.1f MB/s)", - percentage, bytes_processed / (1024*1024), total_bytes / (1024*1024), rate); - } - fflush(stdout); - } - - // Show entropy samples every 64MB for visual verification of randomness - if (bytes_processed % (64 * 1024 * 1024) == 0 && bytes_processed > 0) { - printf("\nšŸ”¬ TrueRNG entropy sample: "); - // Display first 16 bytes of current entropy chunk as hex - size_t sample_size = (current_chunk_size < 16) ? current_chunk_size : 16; - for (size_t i = 0; i < sample_size; i++) { - printf("%02x", entropy_chunk[i]); - if (i == 7) printf(" "); // Space in middle for readability - } - printf("\n"); - } - } - } - - // Cleanup - free(entropy_chunk); - free(pad_chunk); - free(mixed_chunk); - fclose(pad_file); - close(serial_fd); - - // Restore read-only permissions - if (chmod(pad_path, S_IRUSR) != 0) { - if (display_progress) { - printf("Warning: Cannot restore pad file to read-only\n"); - } - } - - if (display_progress) { - double collection_time = difftime(time(NULL), start_time); - printf("\nāœ“ TrueRNG streaming entropy collection complete!\n"); - printf(" Enhanced: %zu bytes in %.1f seconds\n", bytes_processed, collection_time); - printf(" Device: %s\n", get_truerng_device_name(device_type)); - if (collection_time > 0) { - printf(" Rate: %.1f MB/s\n", (double)bytes_processed / collection_time / (1024.0*1024.0)); - } - printf("āœ“ Pad integrity maintained\n"); - printf("āœ“ Entropy distributed across entire pad\n"); - printf("āœ“ Pad restored to read-only mode\n"); - - // Pause before returning to menu to let user see the success message - print_centered_header("TrueRNG Enhancement Complete", 1); - } - - return 0; // Success -} - -// Collect dice entropy with manual input validation -int collect_dice_entropy(unsigned char* entropy_buffer, size_t target_bytes, - size_t* collected_bytes, int display_progress) { - if (display_progress) { - print_centered_header("Dice Entropy Collection", 0); - printf("Enter dice rolls as sequences of digits 1-6.\n"); - printf("Target: %zu bytes (%zu dice rolls needed)\n", target_bytes, target_bytes * 4); - printf("Press Enter after each sequence, or 'done' when finished.\n\n"); - } - - size_t entropy_bits = 0; - size_t target_bits = target_bytes * 8; - unsigned char current_byte = 0; - int bits_in_byte = 0; - size_t bytes_written = 0; - - char input[256]; - - while (entropy_bits < target_bits && bytes_written < target_bytes) { - if (display_progress) { - double percentage = (double)entropy_bits / target_bits * 100.0; - printf("Progress: %.1f%% (%zu/%zu bits) - Enter dice rolls: ", - percentage, entropy_bits, target_bits); - fflush(stdout); - } - - if (!fgets(input, sizeof(input), stdin)) { - if (display_progress) { - printf("Error: Failed to read input\n"); - } - return 1; - } - - // Remove newline - input[strcspn(input, "\n")] = 0; - - // Check for done command - if (strcmp(input, "done") == 0 && entropy_bits >= target_bits / 2) { - break; // Allow early exit if we have at least half the target - } - - // Process dice rolls - for (size_t i = 0; input[i] && entropy_bits < target_bits && bytes_written < target_bytes; i++) { - char c = input[i]; - if (c >= '1' && c <= '6') { - // Convert dice roll (1-6) to 3 bits of entropy - unsigned char roll_value = c - '1'; // 0-5 - - // Pack 3 bits into current byte - for (int bit = 2; bit >= 0 && entropy_bits < target_bits; bit--) { - current_byte = (current_byte << 1) | ((roll_value >> bit) & 1); - bits_in_byte++; - entropy_bits++; - - if (bits_in_byte == 8) { - entropy_buffer[bytes_written++] = current_byte; - current_byte = 0; - bits_in_byte = 0; - } - } - } - } - } - - // Handle partial byte - if (bits_in_byte > 0 && bytes_written < target_bytes) { - // Pad remaining bits with zeros - current_byte <<= (8 - bits_in_byte); - entropy_buffer[bytes_written++] = current_byte; - } - - if (display_progress) { - printf("\nāœ“ Dice entropy collection complete!\n"); - printf(" Collected: %zu bytes from dice rolls\n", bytes_written); - printf(" Entropy bits: %zu\n", entropy_bits); - } - - *collected_bytes = bytes_written; - return 0; // Success -} - -// Collect entropy by source type with unified interface -int collect_entropy_by_source(entropy_source_t source, unsigned char* entropy_buffer, - size_t target_bytes, size_t* collected_bytes, int display_progress) { - switch (source) { - case ENTROPY_SOURCE_KEYBOARD: - return collect_entropy_with_feedback(entropy_buffer, target_bytes, collected_bytes, 1); - - case ENTROPY_SOURCE_TRUERNG: - return collect_truerng_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress); - - case ENTROPY_SOURCE_DICE: - return collect_dice_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress); - - default: - if (display_progress) { - printf("Error: Unknown entropy source\n"); - } - return 1; - } -} - -double get_precise_time(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ts.tv_sec + ts.tv_nsec / 1000000000.0; -} - -void draw_progress_bar(double percentage, int width) { - int filled = (int)(percentage / 100.0 * width); - if (filled > width) filled = width; - - printf("["); - for (int i = 0; i < filled; i++) { - printf("ā–ˆ"); - } - for (int i = filled; i < width; i++) { - printf("ā–‘"); - } - printf("]"); -} - -void draw_quality_bar(double quality, int width, const char* label) { - int filled = (int)(quality / 100.0 * width); - if (filled > width) filled = width; - - // Color coding based on quality - const char* color; - if (quality >= 80) color = "\033[32m"; // Green - else if (quality >= 60) color = "\033[33m"; // Yellow - else color = "\033[31m"; // Red - - printf("%s", color); - draw_progress_bar(quality, width); - printf("\033[0m %-10s", label); // Reset color -} - -double calculate_timing_quality(const entropy_collection_state_t* state) { - // Analyze timing variance between keypresses - if (state->collected_bytes < 32) return 0.0; // Need minimum data - - // Simplified timing quality based on collection rate and variation - double elapsed = get_precise_time() - state->collection_start_time; - if (elapsed < 0.1) return 0.0; - - double rate = state->collected_bytes / elapsed; - - // Optimal rate is around 50-200 bytes/second (moderate typing with good timing variance) - if (rate >= 50 && rate <= 200) return 90.0; - if (rate >= 20 && rate <= 500) return 70.0; - if (rate >= 10 && rate <= 1000) return 50.0; - return 30.0; -} - -double calculate_variety_quality(const entropy_collection_state_t* state) { - // Analyze key variety and distribution - if (state->collected_bytes < 16) return 0.0; - - // Calculate entropy from key histogram - double entropy = 0.0; - size_t total_keys = 0; - - // Count total keypresses - for (int i = 0; i < 256; i++) { - total_keys += state->key_histogram[i]; - } - - if (total_keys == 0) return 0.0; - - // Calculate Shannon entropy - for (int i = 0; i < 256; i++) { - if (state->key_histogram[i] > 0) { - double p = (double)state->key_histogram[i] / total_keys; - entropy -= p * log2(p); - } - } - - // Convert entropy to quality score (0-100) - double max_entropy = log2(256); // Perfect entropy for 8-bit keyspace - double normalized_entropy = entropy / max_entropy; - - // Scale based on unique keys as well - double unique_key_factor = (double)state->unique_keys / 50.0; // 50+ unique keys is excellent - if (unique_key_factor > 1.0) unique_key_factor = 1.0; - - return (normalized_entropy * 70.0 + unique_key_factor * 30.0); -} - -unsigned char calculate_overall_quality(const entropy_collection_state_t* state) { - double timing = calculate_timing_quality(state); - double variety = calculate_variety_quality(state); - - // Simple collection progress bonus - double progress_bonus = (double)state->collected_bytes / state->target_bytes * 20.0; - if (progress_bonus > 20.0) progress_bonus = 20.0; - - // Weighted average - double overall = (timing * 0.4 + variety * 0.4 + progress_bonus); - if (overall > 100.0) overall = 100.0; - - return (unsigned char)overall; -} - -void display_entropy_progress(const entropy_collection_state_t* state) { - // Calculate percentages - double progress = (double)state->collected_bytes / state->target_bytes * 100.0; - if (progress > 100.0) progress = 100.0; - - double quality = state->quality_score; - double timing_quality = calculate_timing_quality(state); - double variety_quality = calculate_variety_quality(state); - - // Clear previous output and redraw - printf("\033[2K\r"); // Clear line - printf("\033[A\033[2K\r"); // Move up and clear - printf("\033[A\033[2K\r"); // Move up and clear - printf("\033[A\033[2K\r"); // Move up and clear - printf("\033[A\033[2K\r"); // Move up and clear - printf("\033[A\033[2K\r"); // Move up and clear - - // Header - printf("Adding Entropy to Pad - Target: %zu bytes\n\n", state->target_bytes); - - // Main progress bar - printf("Progress: "); - draw_progress_bar(progress, 50); - printf(" %.1f%% (%zu/%zu bytes)\n", progress, state->collected_bytes, state->target_bytes); - - // Quality indicators - printf("Quality: "); - draw_quality_bar(quality, 50, "OVERALL"); - printf("\n"); - - printf("Timing: "); - draw_quality_bar(timing_quality, 50, "VARIED"); - printf("\n"); - - printf("Keys: "); - draw_quality_bar(variety_quality, 50, "DIVERSE"); - printf("\n"); - - // Instructions - if (state->collected_bytes >= 1024 && state->auto_complete_enabled) { - printf("\nPress ESC to finish (minimum reached) or continue typing..."); - } else if (state->collected_bytes < 1024) { - printf("\nType random keys... (%zu more bytes needed)", 1024 - state->collected_bytes); - } else { - printf("\nType random keys or press ESC when satisfied..."); - } - - fflush(stdout); -} - -// Chacha20 key derivation from collected entropy -int derive_chacha20_params(const unsigned char* entropy_data, size_t entropy_size, - unsigned char key[32], unsigned char nonce[12]) { - if (!entropy_data || entropy_size < 512 || !key || !nonce) { - return 1; // Error: insufficient entropy or null pointers - } - - // Phase 1: Generate base key from entropy using enhanced XOR checksum method - unsigned char enhanced_checksum[44]; // 32 key + 12 nonce - memset(enhanced_checksum, 0, 44); - - // Mix entropy data similar to calculate_checksum but for 44 bytes - for (size_t i = 0; i < entropy_size; i++) { - unsigned char bucket = i % 44; - enhanced_checksum[bucket] ^= entropy_data[i] ^ - ((i >> 8) & 0xFF) ^ - ((i >> 16) & 0xFF) ^ - ((i >> 24) & 0xFF); - } - - // Phase 2: Add system entropy for additional randomness - unsigned char system_entropy[32]; - FILE* urandom = fopen("/dev/urandom", "rb"); - if (!urandom) { - return 2; // Error: cannot access system entropy - } - - if (fread(system_entropy, 1, 32, urandom) != 32) { - fclose(urandom); - return 2; // Error: insufficient system entropy - } - fclose(urandom); - - // Mix system entropy into derived key - for (int i = 0; i < 32; i++) { - enhanced_checksum[i] ^= system_entropy[i]; - } - - // Extract key and nonce - memcpy(key, enhanced_checksum, 32); - memcpy(nonce, enhanced_checksum + 32, 12); - - return 0; // Success -} - -// Check if a pad is unused (0% usage) -int is_pad_unused(const char* pad_chksum) { - uint64_t used_bytes; - if (read_state_offset(pad_chksum, &used_bytes) != 0) { - return 0; // Error reading state, assume used - } - return (used_bytes <= 32); // Only reserved bytes used (32 bytes for checksum encryption) -} - -// Safely rename pad files (pad and state) from old to new checksum -int rename_pad_files_safely(const char* old_chksum, const char* new_chksum) { - char old_pad_path[1024], new_pad_path[1024]; - char old_state_path[1024], new_state_path[1024]; - - // Construct file paths - snprintf(old_pad_path, sizeof(old_pad_path), "%s/%s.pad", current_pads_dir, old_chksum); - snprintf(new_pad_path, sizeof(new_pad_path), "%s/%s.pad", current_pads_dir, new_chksum); - snprintf(old_state_path, sizeof(old_state_path), "%s/%s.state", current_pads_dir, old_chksum); - snprintf(new_state_path, sizeof(new_state_path), "%s/%s.state", current_pads_dir, new_chksum); - - // Check if new files would conflict with existing files - if (access(new_pad_path, F_OK) == 0) { - printf("Error: New pad file already exists: %s\n", new_pad_path); - return 1; // Conflict - } - - // Rename pad file - if (rename(old_pad_path, new_pad_path) != 0) { - printf("Error: Failed to rename pad file from %s to %s\n", old_pad_path, new_pad_path); - return 2; // Pad rename failed - } - - // Rename state file (if it exists) - if (access(old_state_path, F_OK) == 0) { - if (rename(old_state_path, new_state_path) != 0) { - printf("Warning: Failed to rename state file, but pad file was renamed successfully\n"); - // Try to rollback pad file rename - rename(new_pad_path, old_pad_path); - return 3; // State rename failed - } - } - - return 0; // Success -} - -// Update pad checksum after entropy addition -int update_pad_checksum_after_entropy(const char* old_chksum, char* new_chksum) { - char pad_path[1024]; - snprintf(pad_path, sizeof(pad_path), "%s/%s.pad", current_pads_dir, old_chksum); - - // Calculate new checksum of the modified pad - if (calculate_checksum(pad_path, new_chksum) != 0) { - printf("Error: Cannot calculate new pad checksum\n"); - return 1; - } - - // Check if checksum actually changed - if (strcmp(old_chksum, new_chksum) == 0) { - printf("Warning: Pad checksum unchanged after entropy addition\n"); - return 2; // Checksum didn't change (unusual but not fatal) - } - - // Rename pad files to use new checksum - if (rename_pad_files_safely(old_chksum, new_chksum) != 0) { - return 3; // Rename failed - } - - // Update default pad preference if this was the default pad - char* current_default = get_default_pad_path(); - if (current_default) { - // Check if the old pad was the default - if (strstr(current_default, old_chksum)) { - // Update to new checksum - char new_default_path[1024]; - snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, new_chksum); - - if (set_default_pad_path(new_default_path) != 0) { - printf("Warning: Failed to update default pad preference\n"); - } else { - printf("Updated default pad to new checksum: %.16s...\n", new_chksum); - } - } - free(current_default); - } - - return 0; // Success -} - -// Enhanced entropy collection with visual feedback -int collect_entropy_with_feedback(unsigned char* entropy_buffer, size_t target_bytes, - size_t* collected_bytes, int allow_early_exit) { - struct termios original_termios; - entropy_collection_state_t state = {0}; - - // Initialize state - state.target_bytes = target_bytes; - state.auto_complete_enabled = allow_early_exit; - state.collection_start_time = get_precise_time(); - - // Setup raw terminal - if (setup_raw_terminal(&original_termios) != 0) { - printf("Error: Cannot setup terminal for entropy collection\n"); - return 1; - } - - // Clear screen area for display - printf("\n\n\n\n\n\n"); - - unsigned char entropy_block[16]; - struct timespec timestamp; - uint32_t sequence_counter = 0; - char key; - unsigned char seen_keys[256] = {0}; - - *collected_bytes = 0; - - while (state.collected_bytes < target_bytes) { - // Update display - state.quality_score = calculate_overall_quality(&state); - display_entropy_progress(&state); - - // Non-blocking read - if (read(STDIN_FILENO, &key, 1) == 1) { - // Handle ESC key for early exit - if (key == 27 && allow_early_exit && state.collected_bytes >= 1024) { - break; // Early exit allowed - } - - // Record keypress timing - double current_time = get_precise_time(); - state.last_keypress_time = current_time; - - // Update key histogram - state.key_histogram[(unsigned char)key]++; - - // Get high precision timestamp - clock_gettime(CLOCK_MONOTONIC, ×tamp); - - // Create enhanced entropy block: [key][timestamp][sequence][quality_bits] - entropy_block[0] = key; - memcpy(&entropy_block[1], ×tamp.tv_sec, 8); - memcpy(&entropy_block[9], ×tamp.tv_nsec, 4); - memcpy(&entropy_block[13], &sequence_counter, 2); - entropy_block[15] = (unsigned char)(current_time * 1000) & 0xFF; // Sub-millisecond timing - - // Add to entropy buffer - if (state.collected_bytes + 16 <= MAX_ENTROPY_BUFFER) { - memcpy(entropy_buffer + state.collected_bytes, entropy_block, 16); - state.collected_bytes += 16; - } - - sequence_counter++; - - // Track unique keys - if (!seen_keys[(unsigned char)key]) { - seen_keys[(unsigned char)key] = 1; - state.unique_keys++; - } - } else { - // No key available, just sleep and wait for keystrokes - usleep(10000); // 10ms delay - wait for keystrokes, don't add timing entropy - } - - // Auto-complete at target if enabled - if (state.collected_bytes >= target_bytes) { - break; - } - } - - // Final display update - state.quality_score = calculate_overall_quality(&state); - display_entropy_progress(&state); - - // Summary - double collection_time = get_precise_time() - state.collection_start_time; - printf("\n\nāœ“ Entropy collection complete!\n"); - printf(" Collected: %zu bytes in %.1f seconds\n", state.collected_bytes, collection_time); - printf(" Quality: %d%% (Excellent: 80%%+, Good: 60%%+)\n", state.quality_score); - printf(" Unique keys: %zu\n", state.unique_keys); - - // Restore terminal - restore_terminal(&original_termios); - - *collected_bytes = state.collected_bytes; - return 0; -} - -// Keyboard entropy functions -int setup_raw_terminal(struct termios* original_termios) { - struct termios new_termios; - - if (tcgetattr(STDIN_FILENO, original_termios) != 0) { - return 1; - } - - new_termios = *original_termios; - new_termios.c_lflag &= ~(ICANON | ECHO); - new_termios.c_cc[VMIN] = 0; - new_termios.c_cc[VTIME] = 0; - - if (tcsetattr(STDIN_FILENO, TCSANOW, &new_termios) != 0) { - return 1; - } - - // Set stdin to non-blocking - int flags = fcntl(STDIN_FILENO, F_GETFL); - if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) == -1) { - tcsetattr(STDIN_FILENO, TCSANOW, original_termios); - return 1; - } - - return 0; -} - -void restore_terminal(struct termios* original_termios) { - tcsetattr(STDIN_FILENO, TCSANOW, original_termios); - - // Reset stdin to blocking - int flags = fcntl(STDIN_FILENO, F_GETFL); - fcntl(STDIN_FILENO, F_SETFL, flags & ~O_NONBLOCK); -} - - - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// UNIVERSAL CORE FUNCTIONS FOR CODE CONSOLIDATION -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -// Universal XOR operation - handles both encryption and decryption -// Since XOR is symmetric, this single function replaces all 6 duplicate XOR loops -int universal_xor_operation(const unsigned char* data, size_t data_len, - const unsigned char* pad_data, unsigned char* result) { - if (!data || !pad_data || !result) { - return 1; // Error: null pointer - } - - for (size_t i = 0; i < data_len; i++) { - result[i] = data[i] ^ pad_data[i]; - } - - return 0; // Success -} - -// Universal ASCII message parser - consolidates 4 duplicate parsing implementations -// Extracts checksum, offset, and base64 data from ASCII armored messages -int parse_ascii_message(const char* message, char* chksum, uint64_t* offset, char* base64_data) { - if (!message || !chksum || !offset || !base64_data) { - return 1; // Error: null pointer - } - - char *message_copy = strdup(message); - if (!message_copy) { - return 1; // Memory allocation failed - } - - char *line_ptr = strtok(message_copy, "\n"); - int found_begin = 0; - int in_data_section = 0; - int found_chksum = 0, found_offset = 0; - - // Initialize output - chksum[0] = '\0'; - *offset = 0; - base64_data[0] = '\0'; - - while (line_ptr != NULL) { - if (strcmp(line_ptr, "-----BEGIN OTP MESSAGE-----") == 0) { - found_begin = 1; - } - else if (strcmp(line_ptr, "-----END OTP MESSAGE-----") == 0) { - break; - } - else if (found_begin) { - if (strncmp(line_ptr, "Pad-ChkSum: ", 12) == 0) { - strncpy(chksum, line_ptr + 12, 64); - chksum[64] = '\0'; - found_chksum = 1; - } - else if (strncmp(line_ptr, "Pad-Offset: ", 12) == 0) { - *offset = strtoull(line_ptr + 12, NULL, 10); - found_offset = 1; - } - else if (strlen(line_ptr) == 0) { - in_data_section = 1; - } - else if (in_data_section) { - strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1); - } - else if (strncmp(line_ptr, "Version:", 8) != 0 && strncmp(line_ptr, "Pad-", 4) != 0) { - // This might be base64 data without a blank line separator - strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1); - } - } - line_ptr = strtok(NULL, "\n"); - } - - free(message_copy); - - if (!found_begin || !found_chksum || !found_offset) { - return 2; // Error: incomplete message format - } - - return 0; // Success -} - -// Universal pad data loader - consolidates pad loading and validation logic -// Loads pad data at specified offset and validates pad availability -int load_pad_data(const char* pad_chksum, uint64_t offset, size_t length, unsigned char** pad_data) { - if (!pad_chksum || !pad_data) { - return 1; // Error: null pointer - } - - char pad_path[1024]; - char state_path[1024]; - get_pad_path(pad_chksum, pad_path, state_path); - - // Check if pad file exists - if (access(pad_path, R_OK) != 0) { - return 2; // Error: pad file not found - } - - // Check pad file size - struct stat pad_stat; - if (stat(pad_path, &pad_stat) != 0) { - return 3; // Error: cannot get pad file size - } - - if (offset + length > (uint64_t)pad_stat.st_size) { - return 4; // Error: not enough pad space - } - - // Allocate memory for pad data - *pad_data = malloc(length); - if (!*pad_data) { - return 5; // Error: memory allocation failed - } - - // Open and read pad file - FILE* pad_file = fopen(pad_path, "rb"); - if (!pad_file) { - free(*pad_data); - *pad_data = NULL; - return 6; // Error: cannot open pad file - } - - if (fseek(pad_file, offset, SEEK_SET) != 0) { - fclose(pad_file); - free(*pad_data); - *pad_data = NULL; - return 7; // Error: cannot seek to offset - } - - if (fread(*pad_data, 1, length, pad_file) != length) { - fclose(pad_file); - free(*pad_data); - *pad_data = NULL; - return 8; // Error: cannot read pad data - } - - fclose(pad_file); - return 0; // Success -} - -// Universal ASCII armor generator - consolidates duplicate ASCII armor generation -// Creates ASCII armored output format used by both text and file encryption -int generate_ascii_armor(const char* chksum, uint64_t offset, const unsigned char* encrypted_data, - size_t data_length, char** ascii_output) { - if (!chksum || !encrypted_data || !ascii_output) { - return 1; // Error: null pointer - } - - // Encode data as base64 - char* base64_data = custom_base64_encode(encrypted_data, data_length); - if (!base64_data) { - return 2; // Error: base64 encoding failed - } - - // Calculate required buffer size - size_t base64_len = strlen(base64_data); - size_t header_size = 200; // Approximate size for headers - size_t total_size = header_size + base64_len + (base64_len / 64) + 100; // +newlines +footer - - *ascii_output = malloc(total_size); - if (!*ascii_output) { - free(base64_data); - return 3; // Error: memory allocation failed - } - - // Build ASCII armor - strcpy(*ascii_output, "-----BEGIN OTP MESSAGE-----\n"); - - char temp_line[256]; - snprintf(temp_line, sizeof(temp_line), "Version: v0.3.15\n"); - strcat(*ascii_output, temp_line); - - snprintf(temp_line, sizeof(temp_line), "Pad-ChkSum: %s\n", chksum); - strcat(*ascii_output, temp_line); - - snprintf(temp_line, sizeof(temp_line), "Pad-Offset: %lu\n", offset); - strcat(*ascii_output, temp_line); - - strcat(*ascii_output, "\n"); - - // Add base64 data in 64-character lines - int b64_len = strlen(base64_data); - for (int i = 0; i < b64_len; i += 64) { - char line[70]; - snprintf(line, sizeof(line), "%.64s\n", base64_data + i); - strcat(*ascii_output, line); - } - - strcat(*ascii_output, "-----END OTP MESSAGE-----\n"); - - free(base64_data); - return 0; // Success -} - -// Universal pad integrity validator - consolidates pad validation logic -// Verifies pad checksum matches expected value for security -int validate_pad_integrity(const char* pad_path, const char* expected_chksum) { - if (!pad_path || !expected_chksum) { - return 1; // Error: null pointer - } - - char current_chksum[MAX_HASH_LENGTH]; - if (calculate_checksum(pad_path, current_chksum) != 0) { - return 2; // Error: cannot calculate checksum - } - - if (strcmp(expected_chksum, current_chksum) != 0) { - return 3; // Error: checksum mismatch - } - - return 0; // Success - pad integrity verified -} - -// Universal decrypt function - consolidates all decrypt operations -// input_data: encrypted message text or file path -// output_target: output file path (NULL for stdout/interactive) -// mode: determines behavior and output format -int universal_decrypt(const char* input_data, const char* output_target, decrypt_mode_t mode) { - char stored_chksum[MAX_HASH_LENGTH]; - uint64_t pad_offset; - char base64_data[MAX_INPUT_SIZE * 8] = {0}; - unsigned char* ciphertext = NULL; - int ciphertext_len; - - // Handle input based on mode - if (mode == DECRYPT_MODE_FILE_TO_TEXT || mode == DECRYPT_MODE_FILE_TO_FILE) { - // File input - read the entire file - FILE* input_fp = fopen(input_data, "r"); - if (!input_fp) { - printf("Error: Cannot open input file %s\n", input_data); - return 1; - } - - fseek(input_fp, 0, SEEK_END); - long file_size = ftell(input_fp); - fseek(input_fp, 0, SEEK_SET); - - char* file_content = malloc(file_size + 1); - if (!file_content) { - printf("Error: Memory allocation failed\n"); - fclose(input_fp); - return 1; - } - - size_t bytes_read = fread(file_content, 1, file_size, input_fp); - file_content[bytes_read] = '\0'; - fclose(input_fp); - - // Parse ASCII message from file content - if (parse_ascii_message(file_content, stored_chksum, &pad_offset, base64_data) != 0) { - printf("Error: Invalid ASCII armored format in file\n"); - free(file_content); - return 1; - } - free(file_content); - - if (mode == DECRYPT_MODE_FILE_TO_TEXT) { - printf("Decrypting ASCII armored file...\n"); - } - // Note: DECRYPT_MODE_FILE_TO_FILE should be completely silent for piping - } else { - // Text input (interactive or piped) - const char* message_text; - char full_message[MAX_INPUT_SIZE * 4] = {0}; - - if (input_data != NULL) { - message_text = input_data; - } else { - // Interactive mode - read from stdin - if (mode == DECRYPT_MODE_INTERACTIVE) { - printf("Enter encrypted message (paste the full ASCII armor block):\n"); - } - - char line[MAX_LINE_LENGTH]; - while (fgets(line, sizeof(line), stdin)) { - strncat(full_message, line, sizeof(full_message) - strlen(full_message) - 1); - if (strstr(line, "-----END OTP MESSAGE-----")) { - break; - } - } - message_text = full_message; - } - - // Parse ASCII message from text - if (parse_ascii_message(message_text, stored_chksum, &pad_offset, base64_data) != 0) { - if (mode == DECRYPT_MODE_SILENT) { - fprintf(stderr, "Error: Invalid message format - missing BEGIN header\n"); - } else { - printf("Error: Invalid message format - missing BEGIN header\n"); - } - return 1; - } - } - - // Get pad path and check existence - char pad_path[MAX_HASH_LENGTH + 20]; - char state_path[MAX_HASH_LENGTH + 20]; - get_pad_path(stored_chksum, pad_path, state_path); - - if (access(pad_path, R_OK) != 0) { - if (mode == DECRYPT_MODE_SILENT) { - fprintf(stderr, "Error: Required pad not found: %s\n", stored_chksum); - } else { - printf("Error: Required pad not found: %s\n", stored_chksum); - if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) { - printf("Available pads:\n"); - char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0); - if (selected) { - free(selected); - } - } - } - return 1; - } - - // Validate pad integrity - int integrity_result = validate_pad_integrity(pad_path, stored_chksum); - if (integrity_result == 3) { - if (mode == DECRYPT_MODE_SILENT) { - fprintf(stderr, "Error: Pad integrity check failed!\n"); - return 1; - } else if (mode == DECRYPT_MODE_INTERACTIVE) { - printf("Warning: Pad integrity check failed!\n"); - printf("Expected: %s\n", stored_chksum); - printf("Continue anyway? (y/N): "); - fflush(stdout); - - char response[10]; - if (fgets(response, sizeof(response), stdin) == NULL || - (response[0] != 'y' && response[0] != 'Y')) { - printf("Decryption aborted.\n"); - return 1; - } - } - } else if (integrity_result != 0) { - if (mode == DECRYPT_MODE_SILENT) { - fprintf(stderr, "Error: Cannot verify pad integrity\n"); - } else { - printf("Error: Cannot verify pad integrity\n"); - } - return 1; - } else { - if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) { - printf("Pad integrity: VERIFIED\n"); - } - } - - // Decode base64 ciphertext - ciphertext = custom_base64_decode(base64_data, &ciphertext_len); - if (!ciphertext) { - if (mode == DECRYPT_MODE_SILENT) { - fprintf(stderr, "Error: Invalid base64 data\n"); - } else { - printf("Error: Invalid base64 data\n"); - } - return 1; - } - - // Load pad data using universal function - unsigned char* pad_data; - if (load_pad_data(stored_chksum, pad_offset, ciphertext_len, &pad_data) != 0) { - if (mode == DECRYPT_MODE_SILENT) { - fprintf(stderr, "Error: Cannot load pad data\n"); - } else { - printf("Error: Cannot load pad data\n"); - } - free(ciphertext); - return 1; - } - - // Decrypt using universal XOR operation - if (universal_xor_operation(ciphertext, ciphertext_len, pad_data, ciphertext) != 0) { - if (mode == DECRYPT_MODE_SILENT) { - fprintf(stderr, "Error: Decryption operation failed\n"); - } else { - printf("Error: Decryption operation failed\n"); - } - free(ciphertext); - free(pad_data); - return 1; - } - - // Output based on mode - if (mode == DECRYPT_MODE_FILE_TO_FILE) { - // Write to output file - const char* output_file = output_target; - - // Generate default output filename if not provided - char default_output[512]; - if (output_file == NULL) { - strncpy(default_output, input_data, sizeof(default_output) - 1); - default_output[sizeof(default_output) - 1] = '\0'; - - char* ext = strstr(default_output, ".otp.asc"); - if (ext) { - *ext = '\0'; - } else { - strncat(default_output, ".decrypted", sizeof(default_output) - strlen(default_output) - 1); - } - output_file = default_output; - } - - FILE* output_fp = fopen(output_file, "wb"); - if (!output_fp) { - printf("Error: Cannot create output file %s\n", output_file); - free(ciphertext); - free(pad_data); - return 1; - } - - if (fwrite(ciphertext, 1, ciphertext_len, output_fp) != (size_t)ciphertext_len) { - printf("Error: Cannot write decrypted data\n"); - free(ciphertext); - free(pad_data); - fclose(output_fp); - return 1; - } - fclose(output_fp); - - // Only show success messages in non-silent modes - if (mode != DECRYPT_MODE_FILE_TO_FILE) { - printf("File decrypted successfully: %s\n", output_file); - printf("Note: ASCII format does not preserve original filename/permissions\n"); - } - } else { - // Text output to stdout - need to allocate space for null terminator - char* decrypted_text = malloc(ciphertext_len + 1); - if (!decrypted_text) { - printf("Error: Memory allocation failed for output\n"); - free(ciphertext); - free(pad_data); - return 1; - } - - memcpy(decrypted_text, ciphertext, ciphertext_len); - decrypted_text[ciphertext_len] = '\0'; - - if (mode == DECRYPT_MODE_SILENT) { - // Silent mode - just output the text - printf("%s\n", decrypted_text); - fflush(stdout); - } else { - // Interactive mode - with label - printf("Decrypted: %s\n", decrypted_text); - } - - free(decrypted_text); - } - - // Cleanup - free(ciphertext); - free(pad_data); - - return 0; -} - -// Enhanced input function with editable pre-filled text -int get_filename_with_default(const char* prompt, const char* default_path, char* result, size_t result_size) { - // Find the last directory separator - char* last_slash = strrchr(default_path, '/'); - char directory[1024] = ""; - char filename[512] = ""; - - if (last_slash) { - // Extract directory path - size_t dir_len = last_slash - default_path + 1; // Include the trailing slash - if (dir_len < sizeof(directory)) { - strncpy(directory, default_path, dir_len); - directory[dir_len] = '\0'; - } - - // Extract filename - strncpy(filename, last_slash + 1, sizeof(filename) - 1); - filename[sizeof(filename) - 1] = '\0'; - } else { - // No directory separator, treat as filename only - strncpy(filename, default_path, sizeof(filename) - 1); - filename[sizeof(filename) - 1] = '\0'; - } - - // Setup terminal for raw input - struct termios orig_termios; - if (tcgetattr(STDIN_FILENO, &orig_termios) != 0) { - // Fallback to simple input if terminal control fails - printf("\n%s\n%s: ", prompt, directory); - fflush(stdout); - - char input_buffer[512]; - if (!fgets(input_buffer, sizeof(input_buffer), stdin)) { - return 1; - } - input_buffer[strcspn(input_buffer, "\n")] = 0; - - if (strlen(input_buffer) == 0) { - strncpy(result, default_path, result_size - 1); - } else { - if (strlen(directory) > 0) { - snprintf(result, result_size, "%s%s", directory, input_buffer); - } else { - strncpy(result, input_buffer, result_size - 1); - } - } - result[result_size - 1] = '\0'; - return 0; - } - - // Set up raw terminal mode - struct termios raw_termios = orig_termios; - raw_termios.c_lflag &= ~(ECHO | ICANON); - raw_termios.c_cc[VMIN] = 1; - raw_termios.c_cc[VTIME] = 0; - - if (tcsetattr(STDIN_FILENO, TCSANOW, &raw_termios) != 0) { - // Fallback if terminal setup fails - printf("\n%s\n%s: ", prompt, directory); - fflush(stdout); - - char input_buffer[512]; - if (!fgets(input_buffer, sizeof(input_buffer), stdin)) { - return 1; - } - input_buffer[strcspn(input_buffer, "\n")] = 0; - - if (strlen(input_buffer) == 0) { - strncpy(result, default_path, result_size - 1); - } else { - if (strlen(directory) > 0) { - snprintf(result, result_size, "%s%s", directory, input_buffer); - } else { - strncpy(result, input_buffer, result_size - 1); - } - } - result[result_size - 1] = '\0'; - return 0; - } - - // Display prompt and directory - printf("\n%s\n%s", prompt, directory); - fflush(stdout); - - // Initialize editing buffer with default filename - char edit_buffer[512]; - strncpy(edit_buffer, filename, sizeof(edit_buffer) - 1); - edit_buffer[sizeof(edit_buffer) - 1] = '\0'; - int cursor_pos = strlen(edit_buffer); - int buffer_len = cursor_pos; - - // Display initial filename - printf("%s", edit_buffer); - fflush(stdout); - - // Main editing loop - int c; - while ((c = getchar()) != EOF) { - if (c == '\n' || c == '\r') { - // Enter key - accept input - break; - } else if (c == 127 || c == 8) { - // Backspace/Delete - if (cursor_pos > 0) { - // Move everything after cursor one position left - memmove(&edit_buffer[cursor_pos - 1], &edit_buffer[cursor_pos], buffer_len - cursor_pos + 1); - cursor_pos--; - buffer_len--; - - // Redraw line: move cursor to start of filename area, clear to end, redraw buffer - printf("\r%s\033[K%s", directory, edit_buffer); - - // Position cursor correctly - if (cursor_pos < buffer_len) { - printf("\033[%dD", buffer_len - cursor_pos); - } - fflush(stdout); - } - } else if (c == 27) { - // Escape sequence (arrow keys, etc.) - int c1 = getchar(); - int c2 = getchar(); - if (c1 == '[') { - if (c2 == 'C' && cursor_pos < buffer_len) { - // Right arrow - cursor_pos++; - printf("\033[1C"); - fflush(stdout); - } else if (c2 == 'D' && cursor_pos > 0) { - // Left arrow - cursor_pos--; - printf("\033[1D"); - fflush(stdout); - } else if (c2 == 'H') { - // Home key - printf("\033[%dD", cursor_pos); - cursor_pos = 0; - fflush(stdout); - } else if (c2 == 'F') { - // End key - printf("\033[%dC", buffer_len - cursor_pos); - cursor_pos = buffer_len; - fflush(stdout); - } - } - } else if (c >= 32 && c <= 126) { - // Printable character - if (buffer_len < (int)sizeof(edit_buffer) - 1) { - // Move everything after cursor one position right - memmove(&edit_buffer[cursor_pos + 1], &edit_buffer[cursor_pos], buffer_len - cursor_pos + 1); - edit_buffer[cursor_pos] = c; - cursor_pos++; - buffer_len++; - - // Redraw from cursor position - printf("%c", c); - if (cursor_pos < buffer_len) { - // Print remaining characters and move cursor back - printf("%s\033[%dD", &edit_buffer[cursor_pos], buffer_len - cursor_pos); - } - fflush(stdout); - } - } - // Ignore other control characters - } - - // Restore terminal - tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); - printf("\n"); - - // Construct final result - if (buffer_len == 0) { - // Empty input, use default - strncpy(result, default_path, result_size - 1); - } else { - // Combine directory with edited filename - edit_buffer[buffer_len] = '\0'; - if (strlen(directory) > 0) { - snprintf(result, result_size, "%s%s", directory, edit_buffer); - } else { - strncpy(result, edit_buffer, result_size - 1); - } - } - result[result_size - 1] = '\0'; - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// EDITOR AND FILE MANAGER -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -char* get_preferred_editor(void) { - // Check EDITOR environment variable first - char* editor = getenv("EDITOR"); - if (editor && strlen(editor) > 0) { - // Verify the editor exists - char command[512]; - snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", editor); - if (system(command) == 0) { - return strdup(editor); - } - } - - // Check VISUAL environment variable - editor = getenv("VISUAL"); - if (editor && strlen(editor) > 0) { - char command[512]; - snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", editor); - if (system(command) == 0) { - return strdup(editor); - } - } - - // Try common editors in order of preference - const char* common_editors[] = {"vim", "vi", "nano", "emacs", "gedit", NULL}; - for (int i = 0; common_editors[i] != NULL; i++) { - char command[512]; - snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", common_editors[i]); - if (system(command) == 0) { - return strdup(common_editors[i]); - } - } - - return NULL; // No editor found -} - -char* get_preferred_file_manager(void) { - // Try file managers in order of preference - const char* file_managers[] = {"ranger", "fzf", "nnn", "lf", NULL}; - - for (int i = 0; file_managers[i] != NULL; i++) { - char command[512]; - snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", file_managers[i]); - if (system(command) == 0) { - return strdup(file_managers[i]); - } - } - - return NULL; // No file manager found -} - -int launch_text_editor(const char* initial_content, char* result_buffer, size_t buffer_size) { - char* editor = get_preferred_editor(); - if (!editor) { - printf("Error: No text editor found. Set EDITOR environment variable or install vim/nano.\n"); - return 1; - } - - // Create temporary file - char temp_filename[64]; - snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_edit_%ld.tmp", time(NULL)); - - // Write initial content to temp file if provided - if (initial_content && strlen(initial_content) > 0) { - FILE* temp_file = fopen(temp_filename, "w"); - if (temp_file) { - fputs(initial_content, temp_file); - fclose(temp_file); - } - } else { - // Create empty temp file - FILE* temp_file = fopen(temp_filename, "w"); - if (temp_file) { - fclose(temp_file); - } - } - - // Launch editor - printf("Opening %s for text editing...\n", editor); - char command[512]; - snprintf(command, sizeof(command), "%s %s", editor, temp_filename); - - int result = system(command); - - if (result == 0) { - // Read the edited content back - FILE* temp_file = fopen(temp_filename, "r"); - if (temp_file) { - size_t bytes_read = fread(result_buffer, 1, buffer_size - 1, temp_file); - result_buffer[bytes_read] = '\0'; - - // Remove trailing newline if present - if (bytes_read > 0 && result_buffer[bytes_read - 1] == '\n') { - result_buffer[bytes_read - 1] = '\0'; - } - - fclose(temp_file); - } else { - printf("Error: Cannot read edited content\n"); - free(editor); - unlink(temp_filename); - return 1; - } - } else { - printf("Editor exited with error or was cancelled\n"); - free(editor); - unlink(temp_filename); - return 1; - } - - // Clean up - unlink(temp_filename); - free(editor); - - return 0; -} - -int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size) { - char* fm = get_preferred_file_manager(); - if (!fm) { - printf("No file manager found. Please install ranger, fzf, nnn, or lf.\n"); - printf("Falling back to manual file path entry.\n"); - return 1; // Fall back to manual entry - } - - char temp_filename[64]; - snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_file_%ld.tmp", time(NULL)); - - char command[512]; - int result = 1; - - printf("Opening %s for file selection...\n", fm); - - if (strcmp(fm, "ranger") == 0) { - snprintf(command, sizeof(command), "cd '%s' && ranger --choosefile=%s", - start_directory ? start_directory : ".", temp_filename); - } else if (strcmp(fm, "fzf") == 0) { - snprintf(command, sizeof(command), "cd '%s' && find . -type f | fzf > %s", - start_directory ? start_directory : ".", temp_filename); - } else if (strcmp(fm, "nnn") == 0) { - snprintf(command, sizeof(command), "cd '%s' && nnn -p %s", - start_directory ? start_directory : ".", temp_filename); - } else if (strcmp(fm, "lf") == 0) { - snprintf(command, sizeof(command), "cd '%s' && lf -selection-path=%s", - start_directory ? start_directory : ".", temp_filename); - } - - result = system(command); - - if (result == 0 || result == 256) { // Some file managers return 256 on success - // Read selected file from temp file - FILE* temp_file = fopen(temp_filename, "r"); - if (temp_file) { - if (fgets(selected_file, buffer_size, temp_file)) { - // Remove trailing newline - selected_file[strcspn(selected_file, "\n\r")] = 0; - - // For relative paths from fzf, make absolute if needed - if (selected_file[0] == '.' && selected_file[1] == '/') { - char current_dir[512]; - if (getcwd(current_dir, sizeof(current_dir))) { - char abs_path[1024]; - snprintf(abs_path, sizeof(abs_path), "%s/%s", current_dir, selected_file + 2); - strncpy(selected_file, abs_path, buffer_size - 1); - selected_file[buffer_size - 1] = '\0'; - } - } - - fclose(temp_file); - unlink(temp_filename); - free(fm); - return 0; // Success - } - fclose(temp_file); - } - } - - // Clean up and indicate failure - unlink(temp_filename); - free(fm); - return 1; // Fall back to manual entry -} - -int handle_text_encrypt(void) { - printf("\n"); - print_centered_header("Text Encrypt", 0); - - // Launch text editor directly - char text_buffer[MAX_INPUT_SIZE]; - if (launch_text_editor(NULL, text_buffer, sizeof(text_buffer)) != 0) { - printf("Error: Could not launch text editor\n"); - return 1; - } - - if (strlen(text_buffer) == 0) { - printf("No text entered - canceling encryption\n"); - return 1; - } - - // Use unified pad selection - char* selected_pad = select_pad_interactive("Select Pad for Text Encryption", - "Select pad (by prefix)", - PAD_FILTER_ALL, 1); - if (!selected_pad) { - printf("Text encryption cancelled.\n"); - return 1; - } - - int result = encrypt_text(selected_pad, text_buffer); - free(selected_pad); - return result; -} - -int handle_file_encrypt(void) { - printf("\n"); - print_centered_header("File Encrypt", 0); - - // Launch file manager directly - char input_file[512]; - if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) { - printf("Error: Could not launch file manager\n"); - return 1; - } - - // Check if file exists - if (access(input_file, R_OK) != 0) { - printf("Error: File '%s' not found or cannot be read\n", input_file); - return 1; - } - - // Use unified pad selection - char* selected_pad = select_pad_interactive("Select Pad for File Encryption", - "Select pad (by prefix)", - PAD_FILTER_ALL, 1); - if (!selected_pad) { - printf("File encryption cancelled.\n"); - return 1; - } - - // Ask for output format - printf("\nSelect output format:\n"); - printf("1. Binary (.otp) - preserves file permissions\n"); - printf("2. ASCII (.otp.asc) - text-safe format\n"); - printf("Enter choice (1-2): "); - - char format_input[10]; - if (!fgets(format_input, sizeof(format_input), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - - int ascii_armor = (atoi(format_input) == 2) ? 1 : 0; - - // Generate default output filename - char default_output[1024]; // Increased buffer size to prevent truncation warnings - if (ascii_armor) { - snprintf(default_output, sizeof(default_output), "%s.otp.asc", input_file); - } else { - snprintf(default_output, sizeof(default_output), "%s.otp", input_file); - } - - // Use enhanced input function for output filename - char output_file[512]; - if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { - printf("Error: Failed to read input\n"); - return 1; - } - - const char* output_filename = output_file; - - int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor); - free(selected_pad); - return result; -} - - - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// PADS -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -int calculate_checksum(const char* filename, char* checksum_hex) { - FILE* file = fopen(filename, "rb"); - if (!file) { - return 1; - } - - unsigned char checksum[32]; - unsigned char buffer[64 * 1024]; // 64KB buffer for large files - size_t bytes_read; - - // Initialize checksum - memset(checksum, 0, 32); - size_t total_bytes = 0; - - // Calculate XOR checksum of entire file - while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { - // Process this chunk with XOR checksum - for (size_t i = 0; i < bytes_read; i++) { - unsigned char bucket = (total_bytes + i) % 32; - checksum[bucket] ^= buffer[i] ^ (((total_bytes + i) >> 8) & 0xFF) ^ - (((total_bytes + i) >> 16) & 0xFF) ^ (((total_bytes + i) >> 24) & 0xFF); - } - total_bytes += bytes_read; - } - - fclose(file); - - // Now encrypt the checksum with the first 32 bytes of the pad - fseek(file = fopen(filename, "rb"), 0, SEEK_SET); - unsigned char pad_key[32]; - if (fread(pad_key, 1, 32, file) != 32) { - fclose(file); - return 1; - } - fclose(file); - - // XOR encrypt the checksum with pad data to create unique identifier - unsigned char encrypted_checksum[32]; - for (int i = 0; i < 32; i++) { - encrypted_checksum[i] = checksum[i] ^ pad_key[i]; - } - - // Convert to hex string (64 characters) - for (int i = 0; i < 32; i++) { - sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]); - } - checksum_hex[64] = '\0'; - - return 0; -} - -// Calculate checksum with progress display for large files -int calculate_checksum_with_progress(const char* filename, char* checksum_hex, int display_progress, uint64_t file_size) { - FILE* file = fopen(filename, "rb"); - if (!file) { - return 1; - } - - unsigned char checksum[32]; - unsigned char buffer[64 * 1024]; // 64KB buffer for large files - size_t bytes_read; - - // Initialize checksum - memset(checksum, 0, 32); - size_t total_bytes = 0; - time_t start_time = time(NULL); - - // Calculate XOR checksum of entire file with progress - while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { - // Process this chunk with XOR checksum - for (size_t i = 0; i < bytes_read; i++) { - unsigned char bucket = (total_bytes + i) % 32; - checksum[bucket] ^= buffer[i] ^ (((total_bytes + i) >> 8) & 0xFF) ^ - (((total_bytes + i) >> 16) & 0xFF) ^ (((total_bytes + i) >> 24) & 0xFF); - } - total_bytes += bytes_read; - - // Show progress for large files (every 64MB or if display_progress is enabled) - if (display_progress && file_size > 10 * 1024 * 1024 && total_bytes % (64 * 1024 * 1024) == 0) { - show_progress(total_bytes, file_size, start_time); - } - } - - // Final progress update - if (display_progress && file_size > 10 * 1024 * 1024) { - show_progress(file_size, file_size, start_time); - printf("\n"); - } - - fclose(file); - - // Now encrypt the checksum with the first 32 bytes of the pad - fseek(file = fopen(filename, "rb"), 0, SEEK_SET); - unsigned char pad_key[32]; - if (fread(pad_key, 1, 32, file) != 32) { - fclose(file); - return 1; - } - fclose(file); - - // XOR encrypt the checksum with pad data to create unique identifier - unsigned char encrypted_checksum[32]; - for (int i = 0; i < 32; i++) { - encrypted_checksum[i] = checksum[i] ^ pad_key[i]; - } - - // Convert to hex string (64 characters) - for (int i = 0; i < 32; i++) { - sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]); - } - checksum_hex[64] = '\0'; - - return 0; -} - -// Unified pad selection function - extracts the best UI from handle_pads_menu() -char* select_pad_interactive(const char* title, const char* prompt, pad_filter_type_t filter_type, int allow_cancel) { - // Get list of pads from current directory - DIR* dir = opendir(current_pads_dir); - if (!dir) { - printf("Error: Cannot open pads directory %s\n", current_pads_dir); - return NULL; - } - - // Structure to store pad information - struct PadInfo { - char chksum[65]; - char size_str[32]; - char used_str[32]; - double percentage; - char location[256]; - }; - - struct PadInfo pads[100]; // Support up to 100 pads - int pad_count = 0; - - // Collect all pad information - struct dirent* entry; - while ((entry = readdir(dir)) != NULL && pad_count < 100) { - if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) { - strncpy(pads[pad_count].chksum, entry->d_name, 64); - pads[pad_count].chksum[64] = '\0'; - - // Get pad file size and usage info - char full_path[1024]; - snprintf(full_path, sizeof(full_path), "%s/%s", current_pads_dir, entry->d_name); - struct stat st; - if (stat(full_path, &st) == 0) { - // Get used bytes from state - uint64_t used_bytes; - read_state_offset(pads[pad_count].chksum, &used_bytes); - - // Apply filter - if (filter_type == PAD_FILTER_UNUSED_ONLY && used_bytes > 32) { - continue; // Skip used pads when filtering for unused only - } - - // Format total size - if (st.st_size < 1024) { - snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%luB", st.st_size); - } else if (st.st_size < 1024 * 1024) { - snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fKB", (double)st.st_size / 1024.0); - } else if (st.st_size < 1024 * 1024 * 1024) { - snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fMB", (double)st.st_size / (1024.0 * 1024.0)); - } else { - snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.2fGB", (double)st.st_size / (1024.0 * 1024.0 * 1024.0)); - } - - // Format used size - if (used_bytes < 1024) { - snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%luB", used_bytes); - } else if (used_bytes < 1024 * 1024) { - snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fKB", (double)used_bytes / 1024.0); - } else if (used_bytes < 1024 * 1024 * 1024) { - snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fMB", (double)used_bytes / (1024.0 * 1024.0)); - } else { - snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.2fGB", (double)used_bytes / (1024.0 * 1024.0 * 1024.0)); - } - - // Calculate percentage - pads[pad_count].percentage = (double)used_bytes / st.st_size * 100.0; - - // Set location info using directory display - get_directory_display(full_path, pads[pad_count].location, sizeof(pads[pad_count].location)); - - pad_count++; - } - } - } - closedir(dir); - - if (pad_count == 0) { - printf("\n%s\n", title); - if (filter_type == PAD_FILTER_UNUSED_ONLY) { - printf("No unused pads found.\n"); - printf("Entropy can only be added to pads with 0%% usage (only reserved bytes used).\n"); - } else { - printf("No pads found.\n"); - } - return NULL; - } - - // Calculate minimal unique prefixes for each pad - char prefixes[100][65]; - int prefix_lengths[100]; - - for (int i = 0; i < pad_count; i++) { - prefix_lengths[i] = 1; - - // Find minimal unique prefix - while (prefix_lengths[i] <= 64) { - int unique = 1; - - // Check if current prefix is unique among all other pads - for (int j = 0; j < pad_count; j++) { - if (i != j && strncmp(pads[i].chksum, pads[j].chksum, prefix_lengths[i]) == 0) { - unique = 0; - break; - } - } - - if (unique) { - break; - } - prefix_lengths[i]++; - } - - // Store the minimal prefix - strncpy(prefixes[i], pads[i].chksum, prefix_lengths[i]); - prefixes[i][prefix_lengths[i]] = '\0'; - } - - // Display title and pads table - printf("\n%s\n", title); - printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "ChkSum", "D", "Dir", "Size", "Used", "% Used"); - printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "--------", "--", "------------", "----------", "----------", "------"); - - // Get current default pad path for comparison - char* current_default = get_default_pad_path(); - char default_pad_checksum[65] = ""; - - if (current_default) { - // Extract checksum from default pad path - char* filename = strrchr(current_default, '/'); - if (!filename) filename = current_default; - else filename++; // Skip the '/' - - // Extract checksum (remove .pad extension) - if (strlen(filename) >= 68 && strstr(filename, ".pad")) { - strncpy(default_pad_checksum, filename, 64); - default_pad_checksum[64] = '\0'; - } - free(current_default); - } - - for (int i = 0; i < pad_count; i++) { - // Check if this is the default pad - int is_default = (strlen(default_pad_checksum) > 0 && - strncmp(pads[i].chksum, default_pad_checksum, 64) == 0); - - // Display first 8 characters of checksum with prefix underlined - char checksum_8char[9]; - strncpy(checksum_8char, pads[i].chksum, 8); - checksum_8char[8] = '\0'; - - printf("\033[4m%.*s\033[0m%s %-2s %-12s %-12s %-12s %.1f%%\n", - prefix_lengths[i], checksum_8char, // Underlined prefix - checksum_8char + prefix_lengths[i], // Rest of 8-char checksum - is_default ? "*" : "", // Default indicator - pads[i].location, - pads[i].size_str, - pads[i].used_str, - pads[i].percentage); - } - - // Display prompt - printf("\n%s", prompt); - if (allow_cancel) { - printf(" (or 'x' to cancel)"); - } - printf(": "); - - char input[MAX_HASH_LENGTH]; - if (!fgets(input, sizeof(input), stdin)) { - printf("Error: Failed to read input\n"); - return NULL; - } - input[strcspn(input, "\n")] = 0; - - // Handle empty input - select default pad if available - if (strlen(input) == 0) { - // Get current default pad path - char* current_default = get_default_pad_path(); - if (current_default) { - // Extract checksum from default pad path - char* filename = strrchr(current_default, '/'); - if (!filename) filename = current_default; - else filename++; // Skip the '/' - - // Extract checksum (remove .pad extension) - if (strlen(filename) >= 68 && strstr(filename, ".pad")) { - char default_checksum[65]; - strncpy(default_checksum, filename, 64); - default_checksum[64] = '\0'; - - // Verify this default pad is in our current list - for (int i = 0; i < pad_count; i++) { - if (strncmp(pads[i].chksum, default_checksum, 64) == 0) { - free(current_default); - printf("Selected default pad: %.16s...\n\n", default_checksum); - return strdup(default_checksum); - } - } - } - free(current_default); - } - // No default pad or default pad not in current list - printf("No default pad available or default pad not in current list\n"); - return NULL; - } - - // Handle cancel - if (allow_cancel && (toupper(input[0]) == 'X' && strlen(input) == 1)) { - return NULL; - } - - // Find matching pad by prefix only - int selected_pad = -1; - int match_count = 0; - - // Try prefix matching only - for (int i = 0; i < pad_count; i++) { - if (strncmp(input, pads[i].chksum, strlen(input)) == 0) { - if (match_count == 0) { - selected_pad = i; - } - match_count++; - } - } - - if (match_count == 0) { - printf("No pad found matching '%s'\n", input); - return NULL; - } else if (match_count > 1) { - printf("Ambiguous prefix. Multiple matches found.\n"); - return NULL; - } - - // Return selected pad checksum (caller must free) - return strdup(pads[selected_pad].chksum); -} - -int handle_pads_menu(void) { - printf("\n"); - print_centered_header("Pad Management", 0); - - // Get list of pads from current directory - DIR* dir = opendir(current_pads_dir); - if (!dir) { - printf("Error: Cannot open pads directory %s\n", current_pads_dir); - return 1; - } - - // Structure to store pad information - struct PadInfo { - char chksum[65]; - char size_str[32]; - char used_str[32]; - double percentage; - char location[256]; // Store location info - }; - - struct PadInfo pads[100]; // Support up to 100 pads - int pad_count = 0; - - // Collect all pad information - struct dirent* entry; - while ((entry = readdir(dir)) != NULL && pad_count < 100) { - if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) { - strncpy(pads[pad_count].chksum, entry->d_name, 64); - pads[pad_count].chksum[64] = '\0'; - - // Get pad file size and usage info - char full_path[1024]; // Increased buffer size - snprintf(full_path, sizeof(full_path), "%s/%s", current_pads_dir, entry->d_name); - struct stat st; - if (stat(full_path, &st) == 0) { - // Get used bytes from state - uint64_t used_bytes; - read_state_offset(pads[pad_count].chksum, &used_bytes); - - // Format total size - if (st.st_size < 1024) { - snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%luB", st.st_size); - } else if (st.st_size < 1024 * 1024) { - snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fKB", (double)st.st_size / 1024.0); - } else if (st.st_size < 1024 * 1024 * 1024) { - snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fMB", (double)st.st_size / (1024.0 * 1024.0)); - } else { - snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.2fGB", (double)st.st_size / (1024.0 * 1024.0 * 1024.0)); - } - - // Format used size - if (used_bytes < 1024) { - snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%luB", used_bytes); - } else if (used_bytes < 1024 * 1024) { - snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fKB", (double)used_bytes / 1024.0); - } else if (used_bytes < 1024 * 1024 * 1024) { - snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fMB", (double)used_bytes / (1024.0 * 1024.0)); - } else { - snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.2fGB", (double)used_bytes / (1024.0 * 1024.0 * 1024.0)); - } - - // Calculate percentage - pads[pad_count].percentage = (double)used_bytes / st.st_size * 100.0; - - // Set location info using directory display - get_directory_display(full_path, pads[pad_count].location, sizeof(pads[pad_count].location)); - - pad_count++; - } - } - } - closedir(dir); - - if (pad_count == 0) { - printf("No pads found.\n"); - printf("\nOptions:\n"); - printf(" \033[4mG\033[0menerate new pad\n"); - printf(" E\033[4mx\033[0mit\n"); - printf("\nSelect option: "); - - char input[10]; - if (fgets(input, sizeof(input), stdin)) { - char choice = toupper(input[0]); - if (choice == 'G') { - int result = handle_generate_menu(); - if (result == 0) { - // After successful pad generation, return to pads menu - return handle_pads_menu(); - } - return result; - } - } - return 0; - } - - // Calculate minimal unique prefixes for each pad - char prefixes[100][65]; // Store the minimal prefix for each pad - int prefix_lengths[100]; // Length of minimal prefix for each pad - - for (int i = 0; i < pad_count; i++) { - prefix_lengths[i] = 1; - - // Find minimal unique prefix - while (prefix_lengths[i] <= 64) { - int unique = 1; - - // Check if current prefix is unique among all other pads - for (int j = 0; j < pad_count; j++) { - if (i != j && strncmp(pads[i].chksum, pads[j].chksum, prefix_lengths[i]) == 0) { - unique = 0; - break; - } - } - - if (unique) { - break; - } - prefix_lengths[i]++; - } - - // Store the minimal prefix - strncpy(prefixes[i], pads[i].chksum, prefix_lengths[i]); - prefixes[i][prefix_lengths[i]] = '\0'; - } - - // Display pads with minimal prefixes underlined and default indicator - printf("\nAvailable pads:\n"); - printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "ChkSum", "D", "Dir", "Size", "Used", "% Used"); - printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "--------", "--", "------------", "----------", "----------", "------"); - - // Get current default pad path for comparison - char* current_default = get_default_pad_path(); - char default_pad_checksum[65] = ""; - - if (current_default) { - // Extract checksum from default pad path - char* filename = strrchr(current_default, '/'); - if (!filename) filename = current_default; - else filename++; // Skip the '/' - - // Extract checksum (remove .pad extension) - if (strlen(filename) >= 68 && strstr(filename, ".pad")) { - strncpy(default_pad_checksum, filename, 64); - default_pad_checksum[64] = '\0'; - } - free(current_default); - } - - for (int i = 0; i < pad_count; i++) { - // Check if this is the default pad - int is_default = (strlen(default_pad_checksum) > 0 && - strncmp(pads[i].chksum, default_pad_checksum, 64) == 0); - - // Display first 8 characters of checksum with prefix underlined - char checksum_8char[9]; - strncpy(checksum_8char, pads[i].chksum, 8); - checksum_8char[8] = '\0'; - - printf("\033[4m%.*s\033[0m%s %-2s %-12s %-12s %-12s %.1f%%\n", - prefix_lengths[i], checksum_8char, // Underlined prefix - checksum_8char + prefix_lengths[i], // Rest of 8-char checksum - is_default ? "*" : "", // Default indicator - pads[i].location, // Use the stored location info - pads[i].size_str, - pads[i].used_str, - pads[i].percentage); - } - - printf("\nActions:\n"); - printf(" \033[4mG\033[0menerate new pad\n"); - printf(" \033[4mA\033[0mdd entropy to pad\n"); - printf(" \033[4mV\033[0merify pad integrity\n"); - printf(" \033[4mD\033[0melete pad\n"); - printf(" \033[4mS\033[0met default pad\n"); - printf(" E\033[4mx\033[0mit\n"); - printf("\nSelect action: "); - - char input[MAX_HASH_LENGTH]; - if (!fgets(input, sizeof(input), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - input[strcspn(input, "\n")] = 0; - - // Handle actions - if (toupper(input[0]) == 'G') { - int result = handle_generate_menu(); - if (result == 0) { - // After successful pad generation, return to pads menu - return handle_pads_menu(); - } - return result; - } else if (toupper(input[0]) == 'A') { - // Add entropy to pad - use unified function with unused pads filter - char* selected_pad = select_pad_interactive("Select Unused Pad for Entropy Addition", - "Select unused pad (by prefix)", - PAD_FILTER_UNUSED_ONLY, 1); - if (!selected_pad) { - printf("Entropy addition cancelled.\n"); - return handle_pads_menu(); - } - - // Add entropy to the selected unused pad - int result = handle_add_entropy_to_pad(selected_pad); - free(selected_pad); - if (result == 0) { - // Return to pads menu after successful entropy addition - return handle_pads_menu(); - } - return result; - } else if (toupper(input[0]) == 'V') { - // Verify pad integrity - use unified function - char* selected_pad = select_pad_interactive("Select Pad for Verification", - "Select pad to verify (by prefix)", - PAD_FILTER_ALL, 1); - if (!selected_pad) { - printf("Pad verification cancelled.\n"); - return handle_pads_menu(); - } - - // Verify the selected pad - handle_verify_pad(selected_pad); - free(selected_pad); - return handle_pads_menu(); // Always return to pads menu after verification - } else if (toupper(input[0]) == 'D') { - // Delete pad - use unified function - char* selected_pad = select_pad_interactive("Select Pad for Deletion", - "Select pad to delete (by prefix)", - PAD_FILTER_ALL, 1); - if (!selected_pad) { - printf("Pad deletion cancelled.\n"); - return handle_pads_menu(); - } - - // Delete the selected pad - handle_delete_pad(selected_pad); - free(selected_pad); - return handle_pads_menu(); // Always return to pads menu after deletion attempt - } else if (toupper(input[0]) == 'S') { - // Set default pad - use unified function - char* selected_pad = select_pad_interactive("Select Default Pad", - "Select pad to set as default (by prefix)", - PAD_FILTER_ALL, 1); - if (!selected_pad) { - printf("Default pad selection cancelled.\n"); - return handle_pads_menu(); - } - - // Construct the full absolute pad path and set as default - char new_default_path[1024]; - if (current_pads_dir[0] == '/') { - // Already absolute path - int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad); - if (ret >= (int)sizeof(new_default_path)) { - printf("Error: Path too long for default pad setting\n"); - free(selected_pad); - return handle_pads_menu(); - } - } else { - // Relative path - make it absolute - char current_dir[512]; - if (getcwd(current_dir, sizeof(current_dir))) { - int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s/%s.pad", current_dir, current_pads_dir, selected_pad); - if (ret >= (int)sizeof(new_default_path)) { - // Path was truncated, fall back to relative path - int ret2 = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad); - if (ret2 >= (int)sizeof(new_default_path)) { - printf("Error: Path too long for default pad setting\n"); - free(selected_pad); - return handle_pads_menu(); - } - } - } else { - // Fallback to relative path - int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad); - if (ret >= (int)sizeof(new_default_path)) { - printf("Error: Path too long for default pad setting\n"); - free(selected_pad); - return handle_pads_menu(); - } - } - } - - if (set_default_pad_path(new_default_path) == 0) { - printf("Default pad set to: %.16s...\n", selected_pad); - printf("Full path: %s\n", new_default_path); - } else { - printf("Error: Failed to update default pad preference\n"); - } - - free(selected_pad); - return handle_pads_menu(); - } else if (toupper(input[0]) == 'X') { - return 0; // Exit to main menu - } else { - printf("Invalid action. Please select G, A, S, or X.\n"); - return handle_pads_menu(); - } -} - -int handle_add_entropy_to_pad(const char* pad_chksum) { - char header_text[128]; - snprintf(header_text, sizeof(header_text), "Add Entropy to Pad: %.16s...", pad_chksum); - printf("\n"); - print_centered_header(header_text, 0); - - // Present entropy source selection menu with consistent formatting - printf("Select entropy source:\n"); - printf(" \033[4mK\033[0meyboard entropy - Random typing for entropy collection\n"); - printf(" \033[4mD\033[0mice rolls - Manual dice roll input for high-quality entropy\n"); - printf(" \033[4mT\033[0mrueRNG devices - Hardware random number generators\n"); - printf(" E\033[4mx\033[0mit\n"); - printf("\nSelect option: "); - - char source_input[10]; - if (!fgets(source_input, sizeof(source_input), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - - char choice = toupper(source_input[0]); - entropy_source_t entropy_source; - - switch (choice) { - case 'K': - entropy_source = ENTROPY_SOURCE_KEYBOARD; - break; - case 'D': - entropy_source = ENTROPY_SOURCE_DICE; - break; - case 'T': - entropy_source = ENTROPY_SOURCE_TRUERNG; - break; - case 'X': - return 0; // Exit - default: - printf("Invalid choice. Please select K, D, T, or X.\n"); - return 1; - } - - size_t target_bytes; - - // For TrueRNG, automatically use the full pad size - if (entropy_source == ENTROPY_SOURCE_TRUERNG) { - // Get the pad file size - char pad_path[1024]; - char state_path[1024]; - get_pad_path(pad_chksum, pad_path, state_path); - - struct stat pad_stat; - if (stat(pad_path, &pad_stat) != 0) { - printf("Error: Cannot get pad file size\n"); - return 1; - } - - target_bytes = (size_t)pad_stat.st_size; - printf("\nTrueRNG selected - will enhance entire pad with hardware entropy\n"); - printf("Pad size: %.2f GB (%zu bytes)\n", - (double)target_bytes / (1024.0 * 1024.0 * 1024.0), target_bytes); - } else { - // For other entropy sources, show the selection menu - printf("\nEntropy collection options:\n"); - printf(" 1. Recommended (2048 bytes) - Optimal security\n"); - printf(" 2. Minimum (1024 bytes) - Good security\n"); - printf(" 3. Maximum (4096 bytes) - Maximum security\n"); - printf(" 4. Custom amount\n"); - printf("Enter choice (1-4): "); - - char amount_input[10]; - if (!fgets(amount_input, sizeof(amount_input), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - - target_bytes = 2048; // Default - int amount_choice = atoi(amount_input); - - switch (amount_choice) { - case 1: - target_bytes = 2048; - break; - case 2: - target_bytes = 1024; - break; - case 3: - target_bytes = 4096; - break; - case 4: - printf("Enter custom amount (512-8192 bytes): "); - char custom_input[32]; - if (!fgets(custom_input, sizeof(custom_input), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - - size_t custom_amount = (size_t)atoi(custom_input); - if (custom_amount < 512 || custom_amount > 8192) { - printf("Error: Invalid amount. Must be between 512 and 8192 bytes.\n"); - return 1; - } - target_bytes = custom_amount; - break; - default: - target_bytes = 2048; // Default to recommended - break; - } - } - - // For TrueRNG with large pads, use streaming approach - if (entropy_source == ENTROPY_SOURCE_TRUERNG && target_bytes > MAX_ENTROPY_BUFFER) { - printf("\nUsing streaming approach for large pad enhancement...\n"); - - int result = collect_truerng_entropy_streaming(pad_chksum, target_bytes, 1); - - if (result != 0) { - printf("Error: TrueRNG streaming entropy collection failed\n"); - return 1; - } - - // Update checksum after entropy addition - printf("\nšŸ”„ Updating pad checksum...\n"); - char new_chksum[65]; - int checksum_result = update_pad_checksum_after_entropy(pad_chksum, new_chksum); - - if (checksum_result == 0) { - printf("āœ“ Pad checksum updated successfully\n"); - printf(" Old checksum: %.16s...\n", pad_chksum); - printf(" New checksum: %.16s...\n", new_chksum); - printf("āœ“ Pad files renamed to new checksum\n"); - } else if (checksum_result == 2) { - printf("ℹ Checksum unchanged (unusual but not an error)\n"); - } else { - printf("⚠ Warning: Checksum update failed (entropy was added successfully)\n"); - printf(" You may need to manually handle the checksum update\n"); - return 1; - } - - printf("\nšŸŽ‰ SUCCESS! Your entire pad now has enhanced randomness!\n"); - - // Use enhanced pause mechanism instead of simple getchar - print_centered_header("Pad Enhancement Complete", 1); - - return 0; - } - - // For other entropy sources or smaller amounts, use traditional approach - printf("\nCollecting %zu bytes of entropy from selected source...\n", target_bytes); - - // Allocate entropy buffer - unsigned char* entropy_buffer = malloc(MAX_ENTROPY_BUFFER); - if (!entropy_buffer) { - printf("Error: Cannot allocate entropy buffer\n"); - return 1; - } - - // Collect entropy using unified interface - size_t collected_bytes = 0; - int result = collect_entropy_by_source(entropy_source, entropy_buffer, target_bytes, &collected_bytes, 1); - - if (result != 0) { - printf("Error: Entropy collection failed\n"); - free(entropy_buffer); - return 1; - } - - if (collected_bytes < 512) { - printf("Error: Insufficient entropy collected (%zu bytes)\n", collected_bytes); - free(entropy_buffer); - return 1; - } - - printf("\nProcessing entropy and modifying pad...\n"); - - // Add entropy to pad - result = add_entropy_to_pad(pad_chksum, entropy_buffer, collected_bytes, 1); - - // Clear entropy buffer for security - memset(entropy_buffer, 0, MAX_ENTROPY_BUFFER); - free(entropy_buffer); - - if (result != 0) { - printf("Error: Failed to add entropy to pad\n"); - return 1; - } - - printf("\nšŸŽ‰ SUCCESS! Your pad now has enhanced randomness!\n"); - - // Use enhanced pause mechanism instead of simple getchar - print_centered_header("Entropy Enhancement Complete", 1); - - return 0; -} - -int handle_verify_pad(const char* pad_chksum) { - char header_text[128]; - snprintf(header_text, sizeof(header_text), "Verify Pad Integrity: %.16s...", pad_chksum); - printf("\n"); - print_centered_header(header_text, 0); - - // Construct pad file path - char pad_path[1024]; - char state_path[1024]; - get_pad_path(pad_chksum, pad_path, state_path); - - // Check if pad file exists - if (access(pad_path, R_OK) != 0) { - printf("āŒ ERROR: Pad file not found: %s\n", pad_path); - return 1; - } - - printf("Calculating pad checksum...\n"); - - // Calculate actual checksum of the pad file - char calculated_checksum[65]; - if (calculate_checksum(pad_path, calculated_checksum) != 0) { - printf("āŒ ERROR: Failed to calculate pad checksum\n"); - return 1; - } - - // Compare calculated checksum with filename (expected checksum) - if (strcmp(pad_chksum, calculated_checksum) == 0) { - printf("āœ… SUCCESS: Pad integrity verified!\n"); - printf(" Expected: %.16s...\n", pad_chksum); - printf(" Actual: %.16s...\n", calculated_checksum); - printf(" Status: MATCH - Pad is intact and valid\n"); - - // Get additional pad info - struct stat pad_stat; - if (stat(pad_path, &pad_stat) == 0) { - uint64_t used_bytes; - read_state_offset(pad_chksum, &used_bytes); - - double size_gb = (double)pad_stat.st_size / (1024.0 * 1024.0 * 1024.0); - double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0); - double usage_percent = (double)used_bytes / pad_stat.st_size * 100.0; - - printf(" Size: %.2f GB (%lu bytes)\n", size_gb, pad_stat.st_size); - printf(" Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes); - printf(" Usage: %.1f%%\n", usage_percent); - } - - printf("\nāœ… This pad is safe to use for encryption.\n"); - - // Pause before returning to menu to let user see the verification results - print_centered_header("Pad Verification Complete", 1); - return 0; - } else { - printf("āŒ FAILURE: Pad integrity check failed!\n"); - printf(" Expected: %.16s...\n", pad_chksum); - printf(" Actual: %.16s...\n", calculated_checksum); - printf(" Status: MISMATCH - Pad may be corrupted!\n"); - printf("\nāš ļø WARNING: This pad should NOT be used for encryption.\n"); - printf(" The pad file may have been modified or corrupted.\n"); - printf(" Consider regenerating this pad or using a different one.\n"); - return 1; - } -} - -int handle_delete_pad(const char* pad_chksum) { - char header_text[128]; - snprintf(header_text, sizeof(header_text), "Delete Pad: %.16s...", pad_chksum); - printf("\n"); - print_centered_header(header_text, 0); - - // Construct pad and state file paths - char pad_path[1024]; - char state_path[1024]; - get_pad_path(pad_chksum, pad_path, state_path); - - // Check if pad file exists - if (access(pad_path, F_OK) != 0) { - printf("āŒ ERROR: Pad file not found: %s\n", pad_path); - return 1; - } - - // Get pad information for display - struct stat pad_stat; - if (stat(pad_path, &pad_stat) == 0) { - uint64_t used_bytes; - read_state_offset(pad_chksum, &used_bytes); - - double size_gb = (double)pad_stat.st_size / (1024.0 * 1024.0 * 1024.0); - double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0); - double usage_percent = (double)used_bytes / pad_stat.st_size * 100.0; - - printf("Pad Information:\n"); - printf(" Checksum: %s\n", pad_chksum); - printf(" Size: %.2f GB (%lu bytes)\n", size_gb, pad_stat.st_size); - printf(" Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes); - printf(" Usage: %.1f%%\n", usage_percent); - printf(" Path: %s\n", pad_path); - } - - // Check if this is the default pad - char* current_default = get_default_pad_path(); - int is_default_pad = 0; - if (current_default) { - // Check if the pad to be deleted is the current default - if (strstr(current_default, pad_chksum)) { - is_default_pad = 1; - printf(" Status: āš ļø This is your DEFAULT pad\n"); - } - free(current_default); - } - - // Warning and confirmation - printf("\nāš ļø WARNING: This action cannot be undone!\n"); - if (is_default_pad) { - printf("āš ļø Deleting the default pad will require setting a new default.\n"); - } - printf("\nAre you absolutely sure you want to delete this pad? (y/N): "); - - char response[10]; - if (!fgets(response, sizeof(response), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - - // Require explicit 'y' or 'Y' to proceed - if (response[0] != 'y' && response[0] != 'Y') { - printf("Pad deletion cancelled.\n"); - return 0; // User cancelled - not an error - } - - // Double confirmation for extra safety - printf("\nFinal confirmation - type 'DELETE' to proceed: "); - char final_response[20]; - if (!fgets(final_response, sizeof(final_response), stdin)) { - printf("Error: Failed to read input\n"); - return 1; - } - - // Remove newline - final_response[strcspn(final_response, "\n")] = 0; - - if (strcmp(final_response, "DELETE") != 0) { - printf("Confirmation text did not match. Pad deletion cancelled.\n"); - return 0; // User didn't confirm - not an error - } - - // Proceed with deletion - printf("\nDeleting pad files...\n"); - - // Delete pad file - if (unlink(pad_path) != 0) { - printf("āŒ ERROR: Failed to delete pad file: %s\n", pad_path); - perror("unlink"); - return 1; - } else { - printf("āœ… Deleted pad file: %s\n", pad_path); - } - - // Delete state file (if it exists) - if (access(state_path, F_OK) == 0) { - if (unlink(state_path) != 0) { - printf("āš ļø WARNING: Failed to delete state file: %s\n", state_path); - perror("unlink"); - // Continue - pad file was deleted successfully - } else { - printf("āœ… Deleted state file: %s\n", state_path); - } - } else { - printf("ā„¹ļø No state file found (this is normal)\n"); - } - - // Handle default pad update if necessary - if (is_default_pad) { - printf("\nšŸ”„ Updating default pad preference...\n"); - - // Clear the current default pad - if (set_preference("default_pad", NULL) == 0) { - printf("āœ… Default pad preference cleared\n"); - printf("ā„¹ļø You can set a new default pad from the pad management menu\n"); - } else { - printf("āš ļø WARNING: Failed to clear default pad preference\n"); - printf(" You may need to manually update your configuration\n"); - } - } - - printf("\nāœ… SUCCESS: Pad deleted successfully!\n"); - printf(" Checksum: %.16s...\n", pad_chksum); - printf(" Both pad and state files have been removed\n"); - - // Pause before returning to menu to let user see the success message - print_centered_header("Pad Deletion Complete", 1); - - return 0; -} - - -void print_usage(const char* program_name) { - printf("OTP Cipher - One Time Pad Implementation v0.3.15\n"); - printf("Built for testing entropy system\n"); - printf("Usage:\n"); - printf(" %s - Interactive mode\n", program_name); - printf(" %s generate|-g - Generate new pad\n", program_name); - printf(" %s encrypt|-e [pad_checksum_prefix] [text] - Encrypt text\n", program_name); - printf(" %s decrypt|-d [encrypted_message] - Decrypt message\n", program_name); - printf(" %s -f [-a] [-o ] - Encrypt file\n", program_name); - printf(" %s list|-l - List available pads\n", program_name); - printf("\nFile Operations:\n"); - printf(" -f - Encrypt file (binary .otp format)\n"); - printf(" -f -a - Encrypt file (ASCII .otp.asc format)\n"); - printf(" -o - Specify output filename\n"); - printf("\nShort flags:\n"); - printf(" -g generate -e encrypt -d decrypt -l list -f file\n"); - printf("\nExamples:\n"); - printf(" %s -e 1a2b3c \"Hello world\" - Encrypt inline text\n", program_name); - printf(" %s -f document.pdf 1a2b - Encrypt file (binary)\n", program_name); - printf(" %s -f document.pdf 1a2b -a - Encrypt file (ASCII)\n", program_name); - printf(" %s -f document.pdf 1a2b -o secret.otp - Encrypt with custom output\n", program_name); - printf(" %s -d \"-----BEGIN OTP MESSAGE-----...\" - Decrypt message/file\n", program_name); - printf(" %s -d encrypted.otp.asc - Decrypt ASCII file\n", program_name); - printf(" %s -g 1GB - Generate 1GB pad\n", program_name); - printf(" %s -l - List pads\n", program_name); - printf("\nSize examples: 1GB, 5TB, 512MB, 2048 (bytes)\n"); - printf("Pad selection: Full chksum or prefix\n"); -} +char current_pads_dir[512] = DEFAULT_PADS_DIR; diff --git a/otp.h b/otp.h index 86e0f50..0f11ddf 100644 --- a/otp.h +++ b/otp.h @@ -134,7 +134,8 @@ int decrypt_ascii_file(const char* input_file, const char* output_file); typedef enum { ENTROPY_SOURCE_KEYBOARD = 1, ENTROPY_SOURCE_DICE = 2, - ENTROPY_SOURCE_TRUERNG = 3 + ENTROPY_SOURCE_TRUERNG = 3, + ENTROPY_SOURCE_FILE = 4 } entropy_source_t; // Terminal control for entropy collection @@ -156,17 +157,45 @@ void draw_quality_bar(double quality, int width, const char* label); #define TRUERNGPROV2_VID "04D8" #define TRUERNGPROV2_PID "EBB5" -// TrueRNG Device Type enumeration +// SwiftRNG Device Constants (same VID/PID as TrueRNG devices) +#define SWIFT_RNG_VID "04D8" +#define SWIFT_RNG_PID "F5FE" +#define SWIFT_RNG_PRO_VID "16D0" +#define SWIFT_RNG_PRO_PID "0AA0" +#define SWIFT_RNG_PRO_V2_VID "04D8" +#define SWIFT_RNG_PRO_V2_PID "EBB5" + +// TrueRNG/SwiftRNG Device Type enumeration typedef enum { TRUERNG_ORIGINAL = 1, TRUERNG_PRO = 2, - TRUERNG_PRO_V2 = 3 + TRUERNG_PRO_V2 = 3, + SWIFT_RNG = 4, + SWIFT_RNG_PRO = 5, + SWIFT_RNG_PRO_V2 = 6 } truerng_device_type_t; +// Hardware RNG device information structure +typedef struct { + char port_path[256]; // Device port path (e.g., /dev/ttyUSB0) + truerng_device_type_t device_type; // Device type identifier + char friendly_name[64]; // Human-readable device name + int is_working; // 1 if device passes basic test, 0 otherwise +} hardware_rng_device_t; + +// Hardware RNG device detection and selection functions +int detect_all_hardware_rng_devices(hardware_rng_device_t* devices, int max_devices, int* num_devices_found); +int test_hardware_rng_device(const hardware_rng_device_t* device); +int select_hardware_rng_device_interactive(hardware_rng_device_t* devices, int num_devices, hardware_rng_device_t* selected_device); +int find_truerng_port(char* port_path, size_t port_path_size, truerng_device_type_t* device_type); // Legacy function for backward compatibility + // TrueRNG entropy collection functions (updated to match implementation) -int find_truerng_port(char* port_path, size_t port_path_size, truerng_device_type_t* device_type); int setup_truerng_serial_port(const char* port_path); int collect_truerng_entropy(unsigned char* entropy_buffer, size_t target_bytes, size_t* collected_bytes, int display_progress); +int collect_truerng_entropy_from_device(const hardware_rng_device_t* device, unsigned char* entropy_buffer, + size_t target_bytes, size_t* collected_bytes, int display_progress); +int collect_truerng_entropy_streaming_from_device(const hardware_rng_device_t* device, const char* pad_chksum, + size_t total_bytes, int display_progress, int entropy_mode); const char* get_truerng_device_name(truerng_device_type_t device_type); int read_usb_device_info(const char* port_name, char* vid, char* pid); @@ -184,9 +213,13 @@ double get_precise_time(void); // Entropy processing and application int derive_chacha20_params(const unsigned char* entropy_data, size_t entropy_size, - unsigned char key[32], unsigned char nonce[12]); + unsigned char key[32], unsigned char nonce[12]); int add_entropy_to_pad(const char* pad_chksum, const unsigned char* entropy_data, - size_t entropy_size, int show_progress); + size_t entropy_size, int show_progress); +int add_entropy_direct_xor(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, uint64_t pad_size, int display_progress); +int add_entropy_chacha20(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, uint64_t pad_size, int display_progress); int handle_add_entropy_to_pad(const char* pad_chksum); // Enhanced entropy system helper functions diff --git a/otp.o b/otp.o deleted file mode 100644 index fcc4e39..0000000 Binary files a/otp.o and /dev/null differ diff --git a/src/crypto.c b/src/crypto.c new file mode 100644 index 0000000..5f454f0 --- /dev/null +++ b/src/crypto.c @@ -0,0 +1,1331 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include "../include/otp.h" + +#define PROGRESS_UPDATE_INTERVAL (64 * 1024 * 1024) // 64MB intervals + +// Custom base64 character set +static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const int base64_decode_table[256] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +}; + +// Universal XOR operation - handles both encryption and decryption +// Since XOR is symmetric, this single function replaces all 6 duplicate XOR loops +int universal_xor_operation(const unsigned char* data, size_t data_len, + const unsigned char* pad_data, unsigned char* result) { + if (!data || !pad_data || !result) { + return 1; // Error: null pointer + } + + for (size_t i = 0; i < data_len; i++) { + result[i] = data[i] ^ pad_data[i]; + } + + return 0; // Success +} + +// Custom base64 encode function +char* custom_base64_encode(const unsigned char* input, int length) { + int output_length = 4 * ((length + 2) / 3); + char* encoded = malloc(output_length + 1); + if (!encoded) return NULL; + + int i, j; + for (i = 0, j = 0; i < length;) { + uint32_t octet_a = i < length ? input[i++] : 0; + uint32_t octet_b = i < length ? input[i++] : 0; + uint32_t octet_c = i < length ? input[i++] : 0; + + uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c; + + encoded[j++] = base64_chars[(triple >> 18) & 63]; + encoded[j++] = base64_chars[(triple >> 12) & 63]; + encoded[j++] = base64_chars[(triple >> 6) & 63]; + encoded[j++] = base64_chars[triple & 63]; + } + + // Add padding + for (int pad = 0; pad < (3 - length % 3) % 3; pad++) { + encoded[output_length - 1 - pad] = '='; + } + + encoded[output_length] = '\0'; + return encoded; +} + +// Custom base64 decode function +unsigned char* custom_base64_decode(const char* input, int* output_length) { + int input_length = strlen(input); + if (input_length % 4 != 0) return NULL; + + *output_length = input_length / 4 * 3; + if (input[input_length - 1] == '=') (*output_length)--; + if (input[input_length - 2] == '=') (*output_length)--; + + unsigned char* decoded = malloc(*output_length); + if (!decoded) return NULL; + + int i, j; + for (i = 0, j = 0; i < input_length;) { + int sextet_a = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; + int sextet_b = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; + int sextet_c = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; + int sextet_d = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]]; + + if (sextet_a == -1 || sextet_b == -1 || sextet_c == -1 || sextet_d == -1) { + free(decoded); + return NULL; + } + + uint32_t triple = (sextet_a << 18) + (sextet_b << 12) + (sextet_c << 6) + sextet_d; + + if (j < *output_length) decoded[j++] = (triple >> 16) & 255; + if (j < *output_length) decoded[j++] = (triple >> 8) & 255; + if (j < *output_length) decoded[j++] = triple & 255; + } + + return decoded; +} +// Extracts checksum, offset, and base64 data from ASCII armored messages +int parse_ascii_message(const char* message, char* chksum, uint64_t* offset, char* base64_data) { + if (!message || !chksum || !offset || !base64_data) { + return 1; // Error: null pointer + } + + size_t msg_len = strlen(message); + char *message_copy = malloc(msg_len + 1); + if (!message_copy) { + return 1; // Memory allocation failed + } + strcpy(message_copy, message); + + char *line_ptr = strtok(message_copy, "\n"); + int found_begin = 0; + int in_data_section = 0; + int found_chksum = 0, found_offset = 0; + + // Initialize output + chksum[0] = '\0'; + *offset = 0; + base64_data[0] = '\0'; + + while (line_ptr != NULL) { + if (strcmp(line_ptr, "-----BEGIN OTP MESSAGE-----") == 0) { + found_begin = 1; + } + else if (strcmp(line_ptr, "-----END OTP MESSAGE-----") == 0) { + break; + } + else if (found_begin) { + if (strncmp(line_ptr, "Pad-ChkSum: ", 12) == 0) { + strncpy(chksum, line_ptr + 12, 64); + chksum[64] = '\0'; + found_chksum = 1; + } + else if (strncmp(line_ptr, "Pad-Offset: ", 12) == 0) { + *offset = strtoull(line_ptr + 12, NULL, 10); + found_offset = 1; + } + else if (strlen(line_ptr) == 0) { + in_data_section = 1; + } + else if (in_data_section) { + strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1); + } + else if (strncmp(line_ptr, "Version:", 8) != 0 && strncmp(line_ptr, "Pad-", 4) != 0) { + // This might be base64 data without a blank line separator + strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1); + } + } + line_ptr = strtok(NULL, "\n"); + } + + free(message_copy); + + if (!found_begin || !found_chksum || !found_offset) { + return 2; // Error: incomplete message format + } + + return 0; // Success +} + +// Creates ASCII armored output format used by both text and file encryption +int generate_ascii_armor(const char* chksum, uint64_t offset, const unsigned char* encrypted_data, + size_t data_length, char** ascii_output) { + if (!chksum || !encrypted_data || !ascii_output) { + return 1; // Error: null pointer + } + + // Encode data as base64 + char* base64_data = custom_base64_encode(encrypted_data, data_length); + if (!base64_data) { + return 2; // Error: base64 encoding failed + } + + // Calculate required buffer size + size_t base64_len = strlen(base64_data); + size_t header_size = 200; // Approximate size for headers + size_t total_size = header_size + base64_len + (base64_len / 64) + 100; // +newlines +footer + + *ascii_output = malloc(total_size); + if (!*ascii_output) { + free(base64_data); + return 3; // Error: memory allocation failed + } + + // Build ASCII armor + strcpy(*ascii_output, "-----BEGIN OTP MESSAGE-----\n"); + + char temp_line[256]; + snprintf(temp_line, sizeof(temp_line), "Version: v0.3.16\n"); + strcat(*ascii_output, temp_line); + + snprintf(temp_line, sizeof(temp_line), "Pad-ChkSum: %s\n", chksum); + strcat(*ascii_output, temp_line); + + snprintf(temp_line, sizeof(temp_line), "Pad-Offset: %lu\n", offset); + strcat(*ascii_output, temp_line); + + strcat(*ascii_output, "\n"); + + // Add base64 data in 64-character lines + int b64_len = strlen(base64_data); + for (int i = 0; i < b64_len; i += 64) { + char line[70]; + snprintf(line, sizeof(line), "%.64s\n", base64_data + i); + strcat(*ascii_output, line); + } + + strcat(*ascii_output, "-----END OTP MESSAGE-----\n"); + + free(base64_data); + return 0; // Success +} + +/* +// Progress display function for long operations - MOVED TO src/util.c +void show_progress(uint64_t current, uint64_t total, time_t start_time) { + double percentage = (double)current / total * 100.0; + time_t current_time = time(NULL); + double elapsed = difftime(current_time, start_time); + double rate = current / elapsed; + double eta = (total - current) / rate; + + printf("\rProgress: %.2f%% (%lu/%lu bytes) - %.2f MB/s - ETA: %.0fs", + percentage, current, total, rate / (1024*1024), eta); + fflush(stdout); +} +*/ + +// Calculate XOR checksum of pad file +int calculate_checksum(const char* filename, char* checksum_hex) { + FILE* file = fopen(filename, "rb"); + if (!file) { + return 1; + } + + unsigned char checksum[32]; + unsigned char buffer[64 * 1024]; // 64KB buffer for large files + size_t bytes_read; + + // Initialize checksum + memset(checksum, 0, 32); + size_t total_bytes = 0; + + // Calculate XOR checksum of entire file + while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { + // Process this chunk with XOR checksum + for (size_t i = 0; i < bytes_read; i++) { + unsigned char bucket = (total_bytes + i) % 32; + checksum[bucket] ^= buffer[i] ^ (((total_bytes + i) >> 8) & 0xFF) ^ + (((total_bytes + i) >> 16) & 0xFF) ^ (((total_bytes + i) >> 24) & 0xFF); + } + total_bytes += bytes_read; + } + + fclose(file); + + // Now encrypt the checksum with the first 32 bytes of the pad + fseek(file = fopen(filename, "rb"), 0, SEEK_SET); + unsigned char pad_key[32]; + if (fread(pad_key, 1, 32, file) != 32) { + fclose(file); + return 1; + } + fclose(file); + + // XOR encrypt the checksum with pad data to create unique identifier + unsigned char encrypted_checksum[32]; + for (int i = 0; i < 32; i++) { + encrypted_checksum[i] = checksum[i] ^ pad_key[i]; + } + + // Convert to hex string (64 characters) + for (int i = 0; i < 32; i++) { + sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]); + } + checksum_hex[64] = '\0'; + + return 0; +} + +int encrypt_text(const char* pad_identifier, const char* input_text) { + char* pad_chksum = find_pad_by_prefix(pad_identifier); + if (!pad_chksum) { + return 1; + } + + char text_buffer[MAX_INPUT_SIZE]; + char chksum_hex[MAX_HASH_LENGTH]; + uint64_t current_offset; + + char pad_path[MAX_HASH_LENGTH + 20]; + char state_path[MAX_HASH_LENGTH + 20]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Check if pad file exists + if (access(pad_path, R_OK) != 0) { + printf("Error: Pad file %s not found\n", pad_path); + free(pad_chksum); + return 1; + } + + // Read current offset + if (read_state_offset(pad_chksum, ¤t_offset) != 0) { + printf("Error: Cannot read state file\n"); + free(pad_chksum); + return 1; + } + + // Ensure we never encrypt before offset 32 (reserved for checksum encryption) + if (current_offset < 32) { + printf("Warning: State offset below reserved area, adjusting to 32\n"); + current_offset = 32; + if (write_state_offset(pad_chksum, current_offset) != 0) { + printf("Warning: Failed to update state file\n"); + } + } + + // Calculate XOR checksum of pad file + if (calculate_checksum(pad_path, chksum_hex) != 0) { + printf("Error: Cannot calculate pad checksum\n"); + free(pad_chksum); + return 1; + } + + // Get input text - either from parameter or user input + if (input_text != NULL) { + // Use provided text + strncpy(text_buffer, input_text, sizeof(text_buffer) - 1); + text_buffer[sizeof(text_buffer) - 1] = '\0'; + } else { + // Get input text from user (interactive mode) + if (get_interactive_mode()) { + printf("\nText input options:\n"); + printf(" 1. Type text directly\n"); + printf(" 2. Use text editor\n"); + printf("Enter choice (1-2): "); + } + + char input_choice[10] = "1"; // Default to direct input in non-interactive mode + if (get_interactive_mode()) { + if (!fgets(input_choice, sizeof(input_choice), stdin)) { + printf("Error: Failed to read input\n"); + free(pad_chksum); + return 1; + } + } + + if (get_interactive_mode() && atoi(input_choice) == 2) { + // Use text editor + if (launch_text_editor(NULL, text_buffer, sizeof(text_buffer)) != 0) { + if (get_interactive_mode()) { + printf("Falling back to direct text input.\n"); + printf("Enter text to encrypt: "); + } + fflush(stdout); + + if (fgets(text_buffer, sizeof(text_buffer), stdin) == NULL) { + printf("Error: Failed to read input\n"); + free(pad_chksum); + return 1; + } + + // Remove newline if present + size_t len = strlen(text_buffer); + if (len > 0 && text_buffer[len - 1] == '\n') { + text_buffer[len - 1] = '\0'; + } + } + } else { + // Direct text input + if (get_interactive_mode()) { + printf("Enter text to encrypt: "); + fflush(stdout); + } + + if (fgets(text_buffer, sizeof(text_buffer), stdin) == NULL) { + printf("Error: Failed to read input\n"); + free(pad_chksum); + return 1; + } + + // Remove newline if present + size_t len = strlen(text_buffer); + if (len > 0 && text_buffer[len - 1] == '\n') { + text_buffer[len - 1] = '\0'; + } + } + } + + size_t input_len = strlen(text_buffer); + if (input_len == 0) { + printf("Error: No input provided\n"); + free(pad_chksum); + return 1; + } + + // Check if we have enough pad space + struct stat pad_stat; + if (stat(pad_path, &pad_stat) != 0) { + printf("Error: Cannot get pad file size\n"); + free(pad_chksum); + return 1; + } + + if (current_offset + input_len > (uint64_t)pad_stat.st_size) { + printf("Error: Not enough pad space remaining\n"); + printf("Need: %lu bytes, Available: %lu bytes\n", + input_len, (uint64_t)pad_stat.st_size - current_offset); + free(pad_chksum); + return 1; + } + + // Read pad data at current offset + FILE* pad_file = fopen(pad_path, "rb"); + if (!pad_file) { + printf("Error: Cannot open pad file\n"); + free(pad_chksum); + return 1; + } + + if (fseek(pad_file, current_offset, SEEK_SET) != 0) { + printf("Error: Cannot seek to offset in pad file\n"); + fclose(pad_file); + free(pad_chksum); + return 1; + } + + unsigned char* pad_data = malloc(input_len); + if (fread(pad_data, 1, input_len, pad_file) != input_len) { + printf("Error: Cannot read pad data\n"); + free(pad_data); + fclose(pad_file); + free(pad_chksum); + return 1; + } + fclose(pad_file); + + // Use universal XOR operation for encryption + unsigned char* ciphertext = malloc(input_len); + if (universal_xor_operation((const unsigned char*)text_buffer, input_len, pad_data, ciphertext) != 0) { + printf("Error: Encryption operation failed\n"); + free(pad_data); + free(ciphertext); + free(pad_chksum); + return 1; + } + + // Update state offset + if (write_state_offset(pad_chksum, current_offset + input_len) != 0) { + printf("Warning: Failed to update state file\n"); + } + + // Use universal ASCII armor generator + char* ascii_output; + if (generate_ascii_armor(chksum_hex, current_offset, ciphertext, input_len, &ascii_output) != 0) { + printf("Error: Failed to generate ASCII armor\n"); + free(pad_data); + free(ciphertext); + free(pad_chksum); + return 1; + } + + // Output with appropriate formatting - clean format for piping, spaced format for interactive + int is_interactive = (input_text == NULL); + + if (is_interactive) { + printf("\n\n\n%s\n\n", ascii_output); + } else { + printf("%s\n", ascii_output); // Add newline for proper piping with tee + } + + // Cleanup + free(pad_data); + free(ciphertext); + free(ascii_output); + free(pad_chksum); + + return 0; +} + +// Universal decrypt function - consolidates all decrypt operations +// input_data: encrypted message text or file path +// output_target: output file path (NULL for stdout/interactive) +// mode: determines behavior and output format +int universal_decrypt(const char* input_data, const char* output_target, decrypt_mode_t mode) { + char stored_chksum[MAX_HASH_LENGTH]; + uint64_t pad_offset; + char base64_data[MAX_INPUT_SIZE * 8] = {0}; + unsigned char* ciphertext = NULL; + int ciphertext_len; + + // Handle input based on mode + if (mode == DECRYPT_MODE_FILE_TO_TEXT || mode == DECRYPT_MODE_FILE_TO_FILE) { + // File input - read the entire file + FILE* input_fp = fopen(input_data, "r"); + if (!input_fp) { + printf("Error: Cannot open input file %s\n", input_data); + return 1; + } + + fseek(input_fp, 0, SEEK_END); + long file_size = ftell(input_fp); + fseek(input_fp, 0, SEEK_SET); + + char* file_content = malloc(file_size + 1); + if (!file_content) { + printf("Error: Memory allocation failed\n"); + fclose(input_fp); + return 1; + } + + size_t bytes_read = fread(file_content, 1, file_size, input_fp); + file_content[bytes_read] = '\0'; + fclose(input_fp); + + // Parse ASCII message from file content + if (parse_ascii_message(file_content, stored_chksum, &pad_offset, base64_data) != 0) { + printf("Error: Invalid ASCII armored format in file\n"); + free(file_content); + return 1; + } + free(file_content); + + if (mode == DECRYPT_MODE_FILE_TO_TEXT) { + printf("Decrypting ASCII armored file...\n"); + } + // Note: DECRYPT_MODE_FILE_TO_FILE should be completely silent for piping + } else { + // Text input (interactive or piped) + const char* message_text; + char full_message[MAX_INPUT_SIZE * 4] = {0}; + + if (input_data != NULL) { + message_text = input_data; + } else { + // Interactive mode - read from stdin + if (mode == DECRYPT_MODE_INTERACTIVE) { + printf("Enter encrypted message (paste the full ASCII armor block):\n"); + } + + char line[MAX_LINE_LENGTH]; + while (fgets(line, sizeof(line), stdin)) { + strncat(full_message, line, sizeof(full_message) - strlen(full_message) - 1); + if (strstr(line, "-----END OTP MESSAGE-----")) { + break; + } + } + message_text = full_message; + } + + // Parse ASCII message from text + if (parse_ascii_message(message_text, stored_chksum, &pad_offset, base64_data) != 0) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Invalid message format - missing BEGIN header\n"); + } else { + printf("Error: Invalid message format - missing BEGIN header\n"); + } + return 1; + } + } + + // Get pad path and check existence + char pad_path[MAX_HASH_LENGTH + 20]; + char state_path[MAX_HASH_LENGTH + 20]; + get_pad_path(stored_chksum, pad_path, state_path); + + if (access(pad_path, R_OK) != 0) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Required pad not found: %s\n", stored_chksum); + } else { + printf("Error: Required pad not found: %s\n", stored_chksum); + if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) { + printf("Available pads:\n"); + char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0); + if (selected) { + free(selected); + } + } + } + return 1; + } + + // Validate pad integrity + int integrity_result = validate_pad_integrity(pad_path, stored_chksum); + if (integrity_result == 3) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Pad integrity check failed!\n"); + return 1; + } else if (mode == DECRYPT_MODE_INTERACTIVE) { + printf("Warning: Pad integrity check failed!\n"); + printf("Expected: %s\n", stored_chksum); + printf("Continue anyway? (y/N): "); + fflush(stdout); + + char response[10]; + if (fgets(response, sizeof(response), stdin) == NULL || + (response[0] != 'y' && response[0] != 'Y')) { + printf("Decryption aborted.\n"); + return 1; + } + } + } else if (integrity_result != 0) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Cannot verify pad integrity\n"); + } else { + printf("Error: Cannot verify pad integrity\n"); + } + return 1; + } else { + if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) { + printf("Pad integrity: VERIFIED\n"); + } + } + + // Decode base64 ciphertext + ciphertext = custom_base64_decode(base64_data, &ciphertext_len); + if (!ciphertext) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Invalid base64 data\n"); + } else { + printf("Error: Invalid base64 data\n"); + } + return 1; + } + + // Load pad data using universal function + unsigned char* pad_data; + if (load_pad_data(stored_chksum, pad_offset, ciphertext_len, &pad_data) != 0) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Cannot load pad data\n"); + } else { + printf("Error: Cannot load pad data\n"); + } + free(ciphertext); + return 1; + } + + // Decrypt using universal XOR operation + if (universal_xor_operation(ciphertext, ciphertext_len, pad_data, ciphertext) != 0) { + if (mode == DECRYPT_MODE_SILENT) { + fprintf(stderr, "Error: Decryption operation failed\n"); + } else { + printf("Error: Decryption operation failed\n"); + } + free(ciphertext); + free(pad_data); + return 1; + } + + // Output based on mode + if (mode == DECRYPT_MODE_FILE_TO_FILE) { + // Write to output file + const char* output_file = output_target; + + // Generate default output filename if not provided + char default_output[512]; + if (output_file == NULL) { + strncpy(default_output, input_data, sizeof(default_output) - 1); + default_output[sizeof(default_output) - 1] = '\0'; + + char* ext = strstr(default_output, ".otp.asc"); + if (ext) { + *ext = '\0'; + } else { + strncat(default_output, ".decrypted", sizeof(default_output) - strlen(default_output) - 1); + } + output_file = default_output; + } + + FILE* output_fp = fopen(output_file, "wb"); + if (!output_fp) { + printf("Error: Cannot create output file %s\n", output_file); + free(ciphertext); + free(pad_data); + return 1; + } + + if (fwrite(ciphertext, 1, ciphertext_len, output_fp) != (size_t)ciphertext_len) { + printf("Error: Cannot write decrypted data\n"); + free(ciphertext); + free(pad_data); + fclose(output_fp); + return 1; + } + fclose(output_fp); + + // Only show success messages in non-silent modes + if (mode != DECRYPT_MODE_FILE_TO_FILE) { + printf("File decrypted successfully: %s\n", output_file); + printf("Note: ASCII format does not preserve original filename/permissions\n"); + } + } else { + // Text output to stdout - need to allocate space for null terminator + char* decrypted_text = malloc(ciphertext_len + 1); + if (!decrypted_text) { + printf("Error: Memory allocation failed for output\n"); + free(ciphertext); + free(pad_data); + return 1; + } + + memcpy(decrypted_text, ciphertext, ciphertext_len); + decrypted_text[ciphertext_len] = '\0'; + + if (mode == DECRYPT_MODE_SILENT) { + // Silent mode - just output the text + printf("%s\n", decrypted_text); + fflush(stdout); + } else { + // Interactive mode - with label + printf("Decrypted: %s\n", decrypted_text); + } + + free(decrypted_text); + } + + // Cleanup + free(ciphertext); + free(pad_data); + + return 0; +} + +int decrypt_text(const char* pad_identifier, const char* encrypted_message) { + // Use universal decrypt function with mode based on global interactive mode detection + (void)pad_identifier; // Suppress unused parameter warning - chksum comes from message + decrypt_mode_t mode = get_interactive_mode() ? DECRYPT_MODE_INTERACTIVE : DECRYPT_MODE_SILENT; + return universal_decrypt(encrypted_message, NULL, mode); +} + +int encrypt_file(const char* pad_identifier, const char* input_file, const char* output_file, int ascii_armor) { + char* pad_chksum = find_pad_by_prefix(pad_identifier); + if (!pad_chksum) { + return 1; + } + + char chksum_hex[MAX_HASH_LENGTH]; + uint64_t current_offset; + + char pad_path[MAX_HASH_LENGTH + 20]; + char state_path[MAX_HASH_LENGTH + 20]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Check if input file exists and get its size + struct stat input_stat; + if (stat(input_file, &input_stat) != 0) { + printf("Error: Input file %s not found\n", input_file); + free(pad_chksum); + return 1; + } + + uint64_t file_size = input_stat.st_size; + if (file_size == 0) { + printf("Error: Input file is empty\n"); + free(pad_chksum); + return 1; + } + + // Check if pad file exists + if (access(pad_path, R_OK) != 0) { + printf("Error: Pad file %s not found\n", pad_path); + free(pad_chksum); + return 1; + } + + // Read current offset + if (read_state_offset(pad_chksum, ¤t_offset) != 0) { + printf("Error: Cannot read state file\n"); + free(pad_chksum); + return 1; + } + + // Ensure we never encrypt before offset 32 + if (current_offset < 32) { + printf("Warning: State offset below reserved area, adjusting to 32\n"); + current_offset = 32; + if (write_state_offset(pad_chksum, current_offset) != 0) { + printf("Warning: Failed to update state file\n"); + } + } + + // Calculate XOR checksum of pad file + if (calculate_checksum(pad_path, chksum_hex) != 0) { + printf("Error: Cannot calculate pad checksum\n"); + free(pad_chksum); + return 1; + } + + // Check if we have enough pad space + struct stat pad_stat; + if (stat(pad_path, &pad_stat) != 0) { + printf("Error: Cannot get pad file size\n"); + free(pad_chksum); + return 1; + } + + if (current_offset + file_size > (uint64_t)pad_stat.st_size) { + printf("Error: Not enough pad space remaining\n"); + printf("Need: %lu bytes, Available: %lu bytes\n", + file_size, (uint64_t)pad_stat.st_size - current_offset); + free(pad_chksum); + return 1; + } + + // Generate output filename if not specified, using files directory + char default_output[512]; + if (output_file == NULL) { + char temp_output[512]; + if (ascii_armor) { + snprintf(temp_output, sizeof(temp_output), "%s.otp.asc", input_file); + } else { + snprintf(temp_output, sizeof(temp_output), "%s.otp", input_file); + } + + // Apply files directory default path + get_default_file_path(temp_output, default_output, sizeof(default_output)); + output_file = default_output; + } + + // Open input file + FILE* input_fp = fopen(input_file, "rb"); + if (!input_fp) { + printf("Error: Cannot open input file %s\n", input_file); + free(pad_chksum); + return 1; + } + + // Open pad file + FILE* pad_file = fopen(pad_path, "rb"); + if (!pad_file) { + printf("Error: Cannot open pad file\n"); + fclose(input_fp); + free(pad_chksum); + return 1; + } + + if (fseek(pad_file, current_offset, SEEK_SET) != 0) { + printf("Error: Cannot seek to offset in pad file\n"); + fclose(input_fp); + fclose(pad_file); + free(pad_chksum); + return 1; + } + + // Read and encrypt file + unsigned char buffer[64 * 1024]; + unsigned char pad_buffer[64 * 1024]; + unsigned char* encrypted_data = malloc(file_size); + uint64_t bytes_processed = 0; + + printf("Encrypting %s...\n", input_file); + + while (bytes_processed < file_size) { + uint64_t chunk_size = sizeof(buffer); + if (file_size - bytes_processed < chunk_size) { + chunk_size = file_size - bytes_processed; + } + + // Read file data + if (fread(buffer, 1, chunk_size, input_fp) != chunk_size) { + printf("Error: Cannot read input file data\n"); + free(encrypted_data); + fclose(input_fp); + fclose(pad_file); + free(pad_chksum); + return 1; + } + + // Read pad data + if (fread(pad_buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot read pad data\n"); + free(encrypted_data); + fclose(input_fp); + fclose(pad_file); + free(pad_chksum); + return 1; + } + + // Use universal XOR operation for encryption + if (universal_xor_operation(buffer, chunk_size, pad_buffer, &encrypted_data[bytes_processed]) != 0) { + printf("Error: Encryption operation failed\n"); + free(encrypted_data); + fclose(input_fp); + fclose(pad_file); + free(pad_chksum); + return 1; + } + + bytes_processed += chunk_size; + + // Show progress for large files (> 10MB) + if (file_size > 10 * 1024 * 1024 && bytes_processed % (1024 * 1024) == 0) { + // show_progress(bytes_processed, file_size, start_time); // MOVED TO src/util.c + } + } + + if (file_size > 10 * 1024 * 1024) { + // show_progress(file_size, file_size, start_time); // MOVED TO src/util.c + printf("\n"); + } + + fclose(input_fp); + fclose(pad_file); + + // Write output file + if (ascii_armor) { + // ASCII armored format - same as message format + FILE* output_fp = fopen(output_file, "w"); + if (!output_fp) { + printf("Error: Cannot create output file %s\n", output_file); + free(encrypted_data); + free(pad_chksum); + return 1; + } + + // Use universal ASCII armor generator + char* ascii_output; + if (generate_ascii_armor(chksum_hex, current_offset, encrypted_data, file_size, &ascii_output) != 0) { + printf("Error: Failed to generate ASCII armor\n"); + fclose(output_fp); + free(encrypted_data); + free(pad_chksum); + return 1; + } + + // Write the ASCII armored output to file + fprintf(output_fp, "%s", ascii_output); + + fclose(output_fp); + free(ascii_output); + } else { + // Binary format + FILE* output_fp = fopen(output_file, "wb"); + if (!output_fp) { + printf("Error: Cannot create output file %s\n", output_file); + free(encrypted_data); + free(pad_chksum); + return 1; + } + + // Write binary header + // Magic: "OTP\0" + fwrite("OTP\0", 1, 4, output_fp); + + // Version: 2 bytes + uint16_t version = 1; + fwrite(&version, sizeof(uint16_t), 1, output_fp); + + // Pad checksum: 32 bytes (binary) + unsigned char pad_chksum_bin[32]; + for (int i = 0; i < 32; i++) { + sscanf(chksum_hex + i*2, "%2hhx", &pad_chksum_bin[i]); + } + fwrite(pad_chksum_bin, 1, 32, output_fp); + + // Pad offset: 8 bytes + fwrite(¤t_offset, sizeof(uint64_t), 1, output_fp); + + + // File mode: 4 bytes + uint32_t file_mode = input_stat.st_mode; + fwrite(&file_mode, sizeof(uint32_t), 1, output_fp); + + // File size: 8 bytes + fwrite(&file_size, sizeof(uint64_t), 1, output_fp); + + // Encrypted data + fwrite(encrypted_data, 1, file_size, output_fp); + + fclose(output_fp); + } + + // Update state offset + if (write_state_offset(pad_chksum, current_offset + file_size) != 0) { + printf("Warning: Failed to update state file\n"); + } + + printf("File encrypted successfully: %s\n", output_file); + if (ascii_armor) { + printf("Format: ASCII armored (.otp.asc)\n"); + } else { + printf("Format: Binary (.otp)\n"); + } + + // Pause before returning to menu to let user see the success message + print_centered_header("File Encryption Complete", 1); + + // Cleanup + free(encrypted_data); + free(pad_chksum); + + return 0; +} + +int decrypt_file(const char* input_file, const char* output_file) { + // Check if input file exists + if (access(input_file, R_OK) != 0) { + printf("Error: Input file %s not found\n", input_file); + return 1; + } + + FILE* input_fp = fopen(input_file, "rb"); + if (!input_fp) { + printf("Error: Cannot open input file %s\n", input_file); + return 1; + } + + // Read first few bytes to determine format + char magic[4]; + if (fread(magic, 1, 4, input_fp) != 4) { + printf("Error: Cannot read file header\n"); + fclose(input_fp); + return 1; + } + + fseek(input_fp, 0, SEEK_SET); // Reset to beginning + + if (memcmp(magic, "OTP\0", 4) == 0) { + // Binary format + return decrypt_binary_file(input_fp, output_file); + } else { + // Assume ASCII armored format, read entire file as text + fclose(input_fp); + return decrypt_ascii_file(input_file, output_file); + } +} + +int decrypt_binary_file(FILE* input_fp, const char* output_file) { + // Read binary header + char magic[4]; + uint16_t version; + unsigned char pad_chksum_bin[32]; + uint64_t pad_offset; + uint32_t file_mode; + uint64_t file_size; + + if (fread(magic, 1, 4, input_fp) != 4 || + fread(&version, sizeof(uint16_t), 1, input_fp) != 1 || + fread(pad_chksum_bin, 1, 32, input_fp) != 32 || + fread(&pad_offset, sizeof(uint64_t), 1, input_fp) != 1 || + fread(&file_mode, sizeof(uint32_t), 1, input_fp) != 1 || + fread(&file_size, sizeof(uint64_t), 1, input_fp) != 1) { + printf("Error: Cannot read binary header\n"); + fclose(input_fp); + return 1; + } + + if (memcmp(magic, "OTP\0", 4) != 0) { + printf("Error: Invalid binary format\n"); + fclose(input_fp); + return 1; + } + + // Convert binary checksum to hex + char pad_chksum_hex[65]; + for (int i = 0; i < 32; i++) { + sprintf(pad_chksum_hex + i*2, "%02x", pad_chksum_bin[i]); + } + pad_chksum_hex[64] = '\0'; + + printf("Decrypting binary file...\n"); + printf("File size: %lu bytes\n", file_size); + + // Check if we have the required pad + char pad_path[MAX_HASH_LENGTH + 20]; + char state_path[MAX_HASH_LENGTH + 20]; + get_pad_path(pad_chksum_hex, pad_path, state_path); + + if (access(pad_path, R_OK) != 0) { + printf("Error: Required pad not found: %s\n", pad_chksum_hex); + printf("Available pads:\n"); + char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0); + if (selected) { + free(selected); + } + fclose(input_fp); + return 1; + } + + // Determine output filename + char default_output[512]; + if (output_file == NULL) { + snprintf(default_output, sizeof(default_output), "decrypted.bin"); + output_file = default_output; + } + + // Read encrypted data + unsigned char* encrypted_data = malloc(file_size); + if (fread(encrypted_data, 1, file_size, input_fp) != file_size) { + printf("Error: Cannot read encrypted data\n"); + free(encrypted_data); + fclose(input_fp); + return 1; + } + fclose(input_fp); + + // Open pad file and decrypt + FILE* pad_file = fopen(pad_path, "rb"); + if (!pad_file) { + printf("Error: Cannot open pad file\n"); + free(encrypted_data); + return 1; + } + + if (fseek(pad_file, pad_offset, SEEK_SET) != 0) { + printf("Error: Cannot seek to offset in pad file\n"); + free(encrypted_data); + fclose(pad_file); + return 1; + } + + unsigned char* pad_data = malloc(file_size); + if (fread(pad_data, 1, file_size, pad_file) != file_size) { + printf("Error: Cannot read pad data\n"); + free(encrypted_data); + free(pad_data); + fclose(pad_file); + return 1; + } + fclose(pad_file); + + // Use universal XOR operation for decryption + if (universal_xor_operation(encrypted_data, file_size, pad_data, encrypted_data) != 0) { + printf("Error: Decryption operation failed\n"); + free(encrypted_data); + free(pad_data); + return 1; + } + + // Write decrypted file + FILE* output_fp = fopen(output_file, "wb"); + if (!output_fp) { + printf("Error: Cannot create output file %s\n", output_file); + free(encrypted_data); + free(pad_data); + return 1; + } + + if (fwrite(encrypted_data, 1, file_size, output_fp) != file_size) { + printf("Error: Cannot write decrypted data\n"); + free(encrypted_data); + free(pad_data); + fclose(output_fp); + return 1; + } + fclose(output_fp); + + // Restore file permissions + if (chmod(output_file, file_mode) != 0) { + printf("Warning: Cannot restore file permissions\n"); + } + + printf("File decrypted successfully: %s\n", output_file); + printf("Restored permissions and metadata\n"); + + // Pause before returning to menu to let user see the success message + print_centered_header("File Decryption Complete", 1); + + // Cleanup + free(encrypted_data); + free(pad_data); + + return 0; +} + +int decrypt_ascii_file(const char* input_file, const char* output_file) { + // Use universal decrypt function with file-to-file mode + return universal_decrypt(input_file, output_file, DECRYPT_MODE_FILE_TO_FILE); +} + +/* +// MOVED TO src/pads.c - commented out here +// Construct pad and state file paths from checksum +void get_pad_path(const char* chksum, char* pad_path, char* state_path) { + snprintf(pad_path, 1024, "%s/%s.pad", current_pads_dir, chksum); + snprintf(state_path, 1024, "%s/%s.state", current_pads_dir, chksum); +} +*/ + +// Universal pad data loader - consolidates pad loading and validation logic +// Loads pad data at specified offset and validates pad availability +int load_pad_data(const char* pad_chksum, uint64_t offset, size_t length, unsigned char** pad_data) { + if (!pad_chksum || !pad_data) { + return 1; // Error: null pointer + } + + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Check if pad file exists + if (access(pad_path, R_OK) != 0) { + return 2; // Error: pad file not found + } + + // Check pad file size + struct stat pad_stat; + if (stat(pad_path, &pad_stat) != 0) { + return 3; // Error: cannot get pad file size + } + + if (offset + length > (uint64_t)pad_stat.st_size) { + return 4; // Error: not enough pad space + } + + // Allocate memory for pad data + *pad_data = malloc(length); + if (!*pad_data) { + return 5; // Error: memory allocation failed + } + + // Open and read pad file + FILE* pad_file = fopen(pad_path, "rb"); + if (!pad_file) { + free(*pad_data); + *pad_data = NULL; + return 6; // Error: cannot open pad file + } + + if (fseek(pad_file, offset, SEEK_SET) != 0) { + fclose(pad_file); + free(*pad_data); + *pad_data = NULL; + return 7; // Error: cannot seek to offset + } + + if (fread(*pad_data, 1, length, pad_file) != length) { + fclose(pad_file); + free(*pad_data); + *pad_data = NULL; + return 8; // Error: cannot read pad data + } + + fclose(pad_file); + return 0; // Success +} + +// Universal pad integrity validator - consolidates pad validation logic +// Verifies pad checksum matches expected value for security +int validate_pad_integrity(const char* pad_path, const char* expected_chksum) { + if (!pad_path || !expected_chksum) { + return 1; // Error: null pointer + } + + char current_chksum[MAX_HASH_LENGTH]; + if (calculate_checksum(pad_path, current_chksum) != 0) { + return 2; // Error: cannot calculate checksum + } + + if (strcmp(expected_chksum, current_chksum) != 0) { + return 3; // Error: checksum mismatch + } + + return 0; // Success - pad integrity verified +} + +// Calculate checksum with progress display for large files +int calculate_checksum_with_progress(const char* filename, char* checksum_hex, int display_progress, uint64_t file_size) { + FILE* file = fopen(filename, "rb"); + if (!file) { + return 1; + } + + unsigned char checksum[32]; + unsigned char buffer[64 * 1024]; // 64KB buffer for large files + size_t bytes_read; + + // Initialize checksum + memset(checksum, 0, 32); + size_t total_bytes = 0; + time_t start_time = time(NULL); + + // Calculate XOR checksum of entire file with progress + while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { + // Process this chunk with XOR checksum + for (size_t i = 0; i < bytes_read; i++) { + unsigned char bucket = (total_bytes + i) % 32; + checksum[bucket] ^= buffer[i] ^ (((total_bytes + i) >> 8) & 0xFF) ^ + (((total_bytes + i) >> 16) & 0xFF) ^ (((total_bytes + i) >> 24) & 0xFF); + } + total_bytes += bytes_read; + + // Show progress for large files (every 64MB or if display_progress is enabled) + if (display_progress && file_size > 10 * 1024 * 1024 && total_bytes % (64 * 1024 * 1024) == 0) { + show_progress(total_bytes, file_size, start_time); + } + } + + // Final progress update + if (display_progress && file_size > 10 * 1024 * 1024) { + show_progress(file_size, file_size, start_time); + printf("\n"); + } + + fclose(file); + + // Now encrypt the checksum with the first 32 bytes of the pad + fseek(file = fopen(filename, "rb"), 0, SEEK_SET); + unsigned char pad_key[32]; + if (fread(pad_key, 1, 32, file) != 32) { + fclose(file); + return 1; + } + fclose(file); + + // XOR encrypt the checksum with pad data to create unique identifier + unsigned char encrypted_checksum[32]; + for (int i = 0; i < 32; i++) { + encrypted_checksum[i] = checksum[i] ^ pad_key[i]; + } + + // Convert to hex string (64 characters) + for (int i = 0; i < 32; i++) { + sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]); + } + checksum_hex[64] = '\0'; + + return 0; +} \ No newline at end of file diff --git a/src/entropy.c b/src/entropy.c new file mode 100644 index 0000000..aeb0b3c --- /dev/null +++ b/src/entropy.c @@ -0,0 +1,810 @@ +#define _POSIX_C_SOURCE 200809L +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../nostr_chacha20.h" +#include "../include/otp.h" + + +// In-place pad entropy addition using Chacha20 or direct XOR +int add_entropy_to_pad(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, int display_progress) { + if (!pad_chksum || !entropy_data || entropy_size < 512) { + printf("Error: Invalid entropy data or insufficient entropy\n"); + return 1; + } + + // Get pad file path + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Check if pad exists and get size + struct stat pad_stat; + if (stat(pad_path, &pad_stat) != 0) { + printf("Error: Pad file not found: %s\n", pad_path); + return 1; + } + + uint64_t pad_size = pad_stat.st_size; + + // Determine entropy addition method based on entropy size vs pad size + if (entropy_size >= pad_size) { + // Use direct XOR when entropy >= pad size + return add_entropy_direct_xor(pad_chksum, entropy_data, entropy_size, pad_size, display_progress); + } else { + // Use ChaCha20 when entropy < pad size + return add_entropy_chacha20(pad_chksum, entropy_data, entropy_size, pad_size, display_progress); + } +} + +// Direct XOR entropy addition for large entropy sources +int add_entropy_direct_xor(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, uint64_t pad_size, int display_progress) { + // Get pad file path + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Open pad file for read/write + FILE* pad_file = fopen(pad_path, "r+b"); + if (!pad_file) { + printf("Error: Cannot open pad file for modification: %s\n", pad_path); + printf("Note: Pad files are read-only. Temporarily changing permissions...\n"); + + // Try to make writable temporarily + if (chmod(pad_path, S_IRUSR | S_IWUSR) != 0) { + printf("Error: Cannot change pad file permissions\n"); + return 1; + } + + pad_file = fopen(pad_path, "r+b"); + if (!pad_file) { + printf("Error: Still cannot open pad file for modification\n"); + // Restore read-only + chmod(pad_path, S_IRUSR); + return 1; + } + } + + if (display_progress) { + printf("Adding entropy to pad using direct XOR...\n"); + printf("Pad size: %.2f GB (%lu bytes)\n", (double)pad_size / (1024.0*1024.0*1024.0), pad_size); + printf("Entropy size: %zu bytes\n", entropy_size); + } + + // Process pad in chunks + unsigned char buffer[64 * 1024]; // 64KB chunks + size_t entropy_offset = 0; + uint64_t offset = 0; + time_t start_time = time(NULL); + + while (offset < pad_size) { + size_t chunk_size = sizeof(buffer); + if (pad_size - offset < chunk_size) { + chunk_size = pad_size - offset; + } + + // Read current pad data + if (fread(buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot read pad data at offset %lu\n", offset); + fclose(pad_file); + chmod(pad_path, S_IRUSR); // Restore read-only + return 1; + } + + // XOR with entropy data (wrap around if entropy smaller than pad) + for (size_t i = 0; i < chunk_size; i++) { + buffer[i] ^= entropy_data[entropy_offset % entropy_size]; + entropy_offset++; + } + + // Seek back and write modified data + if (fseek(pad_file, offset, SEEK_SET) != 0) { + printf("Error: Cannot seek to offset %lu\n", offset); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + if (fwrite(buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot write modified pad data\n"); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + offset += chunk_size; + + // Show progress for large pads + if (display_progress && offset % (64 * 1024 * 1024) == 0) { // Every 64MB + show_progress(offset, pad_size, start_time); + } + } + + fclose(pad_file); + + // Restore read-only permissions + if (chmod(pad_path, S_IRUSR) != 0) { + printf("Warning: Cannot restore pad file to read-only\n"); + } + + if (display_progress) { + show_progress(pad_size, pad_size, start_time); + printf("\nāœ“ Entropy successfully added to pad using direct XOR\n"); + printf("āœ“ Pad integrity maintained\n"); + printf("āœ“ %zu bytes of entropy distributed across entire pad\n", entropy_size); + printf("āœ“ Pad restored to read-only mode\n"); + + // Update checksum after entropy addition + printf("\nšŸ”„ Updating pad checksum...\n"); + char new_chksum[65]; + int checksum_result = update_pad_checksum_after_entropy(pad_chksum, new_chksum); + + if (checksum_result == 0) { + printf("āœ“ Pad checksum updated successfully\n"); + printf(" Old checksum: %.16s...\n", pad_chksum); + printf(" New checksum: %.16s...\n", new_chksum); + printf("āœ“ Pad files renamed to new checksum\n"); + + // Pause before returning to menu to let user see the success message + print_centered_header("Entropy Addition Complete", 1); + } else if (checksum_result == 2) { + printf("ℹ Checksum unchanged (unusual but not an error)\n"); + } else { + printf("⚠ Warning: Checksum update failed (entropy was added successfully)\n"); + printf(" You may need to manually handle the checksum update\n"); + return 1; // Report error despite successful entropy addition + } + } + + return 0; +} + +// ChaCha20 entropy addition for smaller entropy sources +int add_entropy_chacha20(const char* pad_chksum, const unsigned char* entropy_data, + size_t entropy_size, uint64_t pad_size, int display_progress) { + // Derive Chacha20 key and nonce from entropy + unsigned char key[32], nonce[12]; + if (derive_chacha20_params(entropy_data, entropy_size, key, nonce) != 0) { + printf("Error: Failed to derive Chacha20 parameters from entropy\n"); + return 1; + } + + // Get pad file path + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + // Open pad file for read/write + FILE* pad_file = fopen(pad_path, "r+b"); + if (!pad_file) { + printf("Error: Cannot open pad file for modification: %s\n", pad_path); + printf("Note: Pad files are read-only. Temporarily changing permissions...\n"); + + // Try to make writable temporarily + if (chmod(pad_path, S_IRUSR | S_IWUSR) != 0) { + printf("Error: Cannot change pad file permissions\n"); + return 1; + } + + pad_file = fopen(pad_path, "r+b"); + if (!pad_file) { + printf("Error: Still cannot open pad file for modification\n"); + // Restore read-only + chmod(pad_path, S_IRUSR); + return 1; + } + } + + if (display_progress) { + printf("Adding entropy to pad using Chacha20...\n"); + printf("Pad size: %.2f GB (%lu bytes)\n", (double)pad_size / (1024.0*1024.0*1024.0), pad_size); + } + + // Process pad in chunks + unsigned char buffer[64 * 1024]; // 64KB chunks + unsigned char keystream[64 * 1024]; + uint64_t offset = 0; + uint32_t counter = 0; + time_t start_time = time(NULL); + + while (offset < pad_size) { + size_t chunk_size = sizeof(buffer); + if (pad_size - offset < chunk_size) { + chunk_size = pad_size - offset; + } + + // Read current pad data + if (fread(buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot read pad data at offset %lu\n", offset); + fclose(pad_file); + chmod(pad_path, S_IRUSR); // Restore read-only + return 1; + } + + // Generate keystream for this chunk + if (chacha20_encrypt(key, counter, nonce, buffer, keystream, chunk_size) != 0) { + printf("Error: Chacha20 keystream generation failed\n"); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + // XOR existing pad with keystream (adds entropy) + for (size_t i = 0; i < chunk_size; i++) { + buffer[i] ^= keystream[i]; + } + + // Seek back and write modified data + if (fseek(pad_file, offset, SEEK_SET) != 0) { + printf("Error: Cannot seek to offset %lu\n", offset); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + if (fwrite(buffer, 1, chunk_size, pad_file) != chunk_size) { + printf("Error: Cannot write modified pad data\n"); + fclose(pad_file); + chmod(pad_path, S_IRUSR); + return 1; + } + + offset += chunk_size; + counter += (chunk_size + 63) / 64; // Round up for block count + + // Show progress for large pads + if (display_progress && offset % (64 * 1024 * 1024) == 0) { // Every 64MB + show_progress(offset, pad_size, start_time); + } + } + + fclose(pad_file); + + // Restore read-only permissions + if (chmod(pad_path, S_IRUSR) != 0) { + printf("Warning: Cannot restore pad file to read-only\n"); + } + + if (display_progress) { + show_progress(pad_size, pad_size, start_time); + printf("\nāœ“ Entropy successfully added to pad using Chacha20\n"); + printf("āœ“ Pad integrity maintained\n"); + printf("āœ“ %zu bytes of entropy distributed across entire pad\n", entropy_size); + printf("āœ“ Pad restored to read-only mode\n"); + + // Update checksum after entropy addition + printf("\nšŸ”„ Updating pad checksum...\n"); + char new_chksum[65]; + int checksum_result = update_pad_checksum_after_entropy(pad_chksum, new_chksum); + + if (checksum_result == 0) { + printf("āœ“ Pad checksum updated successfully\n"); + printf(" Old checksum: %.16s...\n", pad_chksum); + printf(" New checksum: %.16s...\n", new_chksum); + printf("āœ“ Pad files renamed to new checksum\n"); + + // Pause before returning to menu to let user see the success message + print_centered_header("Entropy Addition Complete", 1); + } else if (checksum_result == 2) { + printf("ℹ Checksum unchanged (unusual but not an error)\n"); + } else { + printf("⚠ Warning: Checksum update failed (entropy was added successfully)\n"); + printf(" You may need to manually handle the checksum update\n"); + return 1; // Report error despite successful entropy addition + } + } + + return 0; +} + +// Enhanced entropy collection with visual feedback +int collect_entropy_with_feedback(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int allow_early_exit) { + struct termios original_termios; + entropy_collection_state_t state = {0}; + + // Initialize state + state.target_bytes = target_bytes; + state.auto_complete_enabled = allow_early_exit; + state.collection_start_time = get_precise_time(); + + // Setup raw terminal + if (setup_raw_terminal(&original_termios) != 0) { + printf("Error: Cannot setup terminal for entropy collection\n"); + return 1; + } + + // Clear screen area for display + printf("\n\n\n\n\n\n"); + printf("\033[2J\033[H"); // Clear screen and move to top + + unsigned char entropy_block[16]; + struct timespec timestamp; + uint32_t sequence_counter = 0; + char key; + unsigned char seen_keys[256] = {0}; + + *collected_bytes = 0; + + while (state.collected_bytes < target_bytes) { + // Update display + state.quality_score = calculate_overall_quality(&state); + display_entropy_progress(&state); + + // Non-blocking read + if (read(STDIN_FILENO, &key, 1) == 1) { + // Handle ESC key for early exit + if (key == 27 && allow_early_exit && state.collected_bytes >= 1024) { + break; // Early exit allowed + } + + // Record keypress timing + double current_time = get_precise_time(); + state.last_keypress_time = current_time; + + // Update key histogram + state.key_histogram[(unsigned char)key]++; + + // Get high precision timestamp + clock_gettime(CLOCK_MONOTONIC, ×tamp); + + // Create enhanced entropy block: [key][timestamp][sequence][quality_bits] + entropy_block[0] = key; + memcpy(&entropy_block[1], ×tamp.tv_sec, 8); + memcpy(&entropy_block[9], ×tamp.tv_nsec, 4); + memcpy(&entropy_block[13], &sequence_counter, 2); + entropy_block[15] = (unsigned char)(current_time * 1000) & 0xFF; // Sub-millisecond timing + + // Add to entropy buffer + if (state.collected_bytes + 16 <= MAX_ENTROPY_BUFFER) { + memcpy(entropy_buffer + state.collected_bytes, entropy_block, 16); + state.collected_bytes += 16; + } + + sequence_counter++; + + // Track unique keys + if (!seen_keys[(unsigned char)key]) { + seen_keys[(unsigned char)key] = 1; + state.unique_keys++; + } + } else { + // No key available, just sleep and wait for keystrokes + usleep(10000); // 10ms delay - wait for keystrokes, don't add timing entropy + } + + // Auto-complete at target if enabled + if (state.collected_bytes >= target_bytes) { + break; + } + } + + // Final display update + state.quality_score = calculate_overall_quality(&state); + display_entropy_progress(&state); + + // Summary + double collection_time = get_precise_time() - state.collection_start_time; + printf("\n\nāœ“ Entropy collection complete!\n"); + printf(" Collected: %zu bytes in %.1f seconds\n", state.collected_bytes, collection_time); + printf(" Quality: %d%% (Excellent: 80%%+, Good: 60%%+)\n", state.quality_score); + printf(" Unique keys: %zu\n", state.unique_keys); + + // Restore terminal + restore_terminal(&original_termios); + + *collected_bytes = state.collected_bytes; + return 0; +} + +// Chacha20 key derivation from collected entropy +int derive_chacha20_params(const unsigned char* entropy_data, size_t entropy_size, + unsigned char key[32], unsigned char nonce[12]) { + if (!entropy_data || entropy_size < 512 || !key || !nonce) { + return 1; // Error: insufficient entropy or null pointers + } + + // Phase 1: Generate base key from entropy using enhanced XOR checksum method + unsigned char enhanced_checksum[44]; // 32 key + 12 nonce + memset(enhanced_checksum, 0, 44); + + // Mix entropy data similar to calculate_checksum but for 44 bytes + for (size_t i = 0; i < entropy_size; i++) { + unsigned char bucket = i % 44; + enhanced_checksum[bucket] ^= entropy_data[i] ^ + ((i >> 8) & 0xFF) ^ + ((i >> 16) & 0xFF) ^ + ((i >> 24) & 0xFF); + } + + // Phase 2: Add system entropy for additional randomness + unsigned char system_entropy[32]; + FILE* urandom = fopen("/dev/urandom", "rb"); + if (!urandom) { + return 2; // Error: cannot access system entropy + } + + if (fread(system_entropy, 1, 32, urandom) != 32) { + fclose(urandom); + return 2; // Error: insufficient system entropy + } + fclose(urandom); + + // Mix system entropy into derived key + for (int i = 0; i < 32; i++) { + enhanced_checksum[i] ^= system_entropy[i]; + } + + // Extract key and nonce + memcpy(key, enhanced_checksum, 32); + memcpy(nonce, enhanced_checksum + 32, 12); + + return 0; // Success +} + +// Collect entropy from binary file +int collect_file_entropy(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int display_progress) { + if (display_progress) { + print_centered_header("File Entropy Collection", 0); + printf("Load entropy from binary file (.bin format)\n"); + printf("Target: %zu bytes\n", target_bytes); + } + + printf("Enter path to binary entropy file: "); + fflush(stdout); + + char file_path[512]; + if (!fgets(file_path, sizeof(file_path), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + // Remove newline + file_path[strcspn(file_path, "\n")] = 0; + + // Check if file exists and get size + struct stat file_stat; + if (stat(file_path, &file_stat) != 0) { + printf("Error: File '%s' not found\n", file_path); + return 1; + } + + if (!S_ISREG(file_stat.st_mode)) { + printf("Error: '%s' is not a regular file\n", file_path); + return 1; + } + + size_t file_size = file_stat.st_size; + if (file_size == 0) { + printf("Error: File is empty\n"); + return 1; + } + + if (file_size < target_bytes) { + printf("Warning: File size (%zu bytes) is smaller than target (%zu bytes)\n", + file_size, target_bytes); + printf("Will read available data and pad with zeros if necessary.\n"); + } + + // Open file for reading + FILE* entropy_file = fopen(file_path, "rb"); + if (!entropy_file) { + printf("Error: Cannot open file '%s' for reading\n", file_path); + return 1; + } + + if (display_progress) { + printf("Reading entropy from file...\n"); + } + + // Read entropy data + size_t bytes_to_read = (file_size < target_bytes) ? file_size : target_bytes; + size_t bytes_read = fread(entropy_buffer, 1, bytes_to_read, entropy_file); + + if (bytes_read != bytes_to_read) { + printf("Error: Failed to read %zu bytes from file (read %zu)\n", + bytes_to_read, bytes_read); + fclose(entropy_file); + return 1; + } + + fclose(entropy_file); + + // Pad with zeros if file was smaller than target + if (bytes_read < target_bytes) { + memset(entropy_buffer + bytes_read, 0, target_bytes - bytes_read); + *collected_bytes = target_bytes; // We padded to target size + } else { + *collected_bytes = bytes_read; + } + + if (display_progress) { + printf("āœ“ File entropy collection complete!\n"); + printf(" File: %s\n", file_path); + printf(" Read: %zu bytes\n", bytes_read); + printf(" Total: %zu bytes (padded to target if necessary)\n", *collected_bytes); + } + + return 0; // Success +} + +// Collect entropy by source type with unified interface +int collect_entropy_by_source(entropy_source_t source, unsigned char* entropy_buffer, + size_t target_bytes, size_t* collected_bytes, int display_progress) { + switch (source) { + case ENTROPY_SOURCE_KEYBOARD: + return collect_entropy_with_feedback(entropy_buffer, target_bytes, collected_bytes, 1); + + case ENTROPY_SOURCE_TRUERNG: + return collect_truerng_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress); + + case ENTROPY_SOURCE_DICE: + return collect_dice_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress); + + case ENTROPY_SOURCE_FILE: + return collect_file_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress); + + default: + if (display_progress) { + printf("Error: Unknown entropy source\n"); + } + return 1; + } +} + +// Collect manual entropy from any printable character input +int collect_dice_entropy(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int display_progress) { + if (display_progress) { + print_centered_header("Manual Entropy Collection", 0); + printf("Enter any text, numbers, or symbols for entropy.\n"); + printf("Target: %zu bytes (%zu characters needed)\n", target_bytes, target_bytes); + printf("Press Enter after each line, or 'done' when finished.\n\n"); + } + + size_t bytes_written = 0; + + char input[256]; + + while (bytes_written < target_bytes) { + if (display_progress) { + double percentage = (double)bytes_written / target_bytes * 100.0; + printf("Progress: %.1f%% (%zu/%zu bytes) - Enter text: ", + percentage, bytes_written, target_bytes); + fflush(stdout); + } + + if (!fgets(input, sizeof(input), stdin)) { + if (display_progress) { + printf("Error: Failed to read input\n"); + } + return 1; + } + + // Remove newline + input[strcspn(input, "\n")] = 0; + + // Check for done command + if (strcmp(input, "done") == 0 && bytes_written >= target_bytes / 2) { + break; // Allow early exit if we have at least half the target + } + + // Process each printable character as 8 bits of entropy + for (size_t i = 0; input[i] && bytes_written < target_bytes; i++) { + char c = input[i]; + if (c >= 32 && c <= 126) { // Printable ASCII characters + entropy_buffer[bytes_written++] = (unsigned char)c; + } + } + } + + if (display_progress) { + printf("\nāœ“ Manual entropy collection complete!\n"); + printf(" Collected: %zu bytes from text input\n", bytes_written); + printf(" Entropy quality: 8 bits per character\n"); + } + + *collected_bytes = bytes_written; + return 0; // Success +} + +void restore_terminal(struct termios* original_termios) { + tcsetattr(STDIN_FILENO, TCSANOW, original_termios); + + // Reset stdin to blocking + int flags = fcntl(STDIN_FILENO, F_GETFL); + fcntl(STDIN_FILENO, F_SETFL, flags & ~O_NONBLOCK); +} +double get_precise_time(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec + ts.tv_nsec / 1000000000.0; +} + +void draw_progress_bar(double percentage, int width) { + int filled = (int)(percentage / 100.0 * width); + if (filled > width) filled = width; + + printf("["); + for (int i = 0; i < filled; i++) { + printf("ā–ˆ"); + } + for (int i = filled; i < width; i++) { + printf("ā–‘"); + } + printf("]"); +} + +void draw_quality_bar(double quality, int width, const char* label) { + int filled = (int)(quality / 100.0 * width); + if (filled > width) filled = width; + + // Color coding based on quality + const char* color; + if (quality >= 80) color = "\033[32m"; // Green + else if (quality >= 60) color = "\033[33m"; // Yellow + else color = "\033[31m"; // Red + + printf("%s", color); + draw_progress_bar(quality, width); + printf("\033[0m %-10s", label); // Reset color +} + +double calculate_timing_quality(const entropy_collection_state_t* state) { + // Analyze timing variance between keypresses + if (state->collected_bytes < 32) return 0.0; // Need minimum data + + // Simplified timing quality based on collection rate and variation + double elapsed = get_precise_time() - state->collection_start_time; + if (elapsed < 0.1) return 0.0; + + double rate = state->collected_bytes / elapsed; + + // Optimal rate is around 50-200 bytes/second (moderate typing with good timing variance) + if (rate >= 50 && rate <= 200) return 90.0; + if (rate >= 20 && rate <= 500) return 70.0; + if (rate >= 10 && rate <= 1000) return 50.0; + return 30.0; +} + +double calculate_variety_quality(const entropy_collection_state_t* state) { + // Analyze key variety and distribution + if (state->collected_bytes < 16) return 0.0; + + // Calculate entropy from key histogram + double entropy = 0.0; + size_t total_keys = 0; + + // Count total keypresses + for (int i = 0; i < 256; i++) { + total_keys += state->key_histogram[i]; + } + + if (total_keys == 0) return 0.0; + + // Calculate Shannon entropy + for (int i = 0; i < 256; i++) { + if (state->key_histogram[i] > 0) { + double p = (double)state->key_histogram[i] / total_keys; + entropy -= p * log2(p); + } + } + + // Convert entropy to quality score (0-100) + double max_entropy = log2(256); // Perfect entropy for 8-bit keyspace + double normalized_entropy = entropy / max_entropy; + + // Scale based on unique keys as well + double unique_key_factor = (double)state->unique_keys / 50.0; // 50+ unique keys is excellent + if (unique_key_factor > 1.0) unique_key_factor = 1.0; + + return (normalized_entropy * 70.0 + unique_key_factor * 30.0); +} + +unsigned char calculate_overall_quality(const entropy_collection_state_t* state) { + double timing = calculate_timing_quality(state); + double variety = calculate_variety_quality(state); + + // Simple collection progress bonus + double progress_bonus = (double)state->collected_bytes / state->target_bytes * 20.0; + if (progress_bonus > 20.0) progress_bonus = 20.0; + + // Weighted average + double overall = (timing * 0.4 + variety * 0.4 + progress_bonus); + if (overall > 100.0) overall = 100.0; + + return (unsigned char)overall; +} + +void display_entropy_progress(const entropy_collection_state_t* state) { + // Calculate percentages + double progress = (double)state->collected_bytes / state->target_bytes * 100.0; + if (progress > 100.0) progress = 100.0; + + double quality = state->quality_score; + double timing_quality = calculate_timing_quality(state); + double variety_quality = calculate_variety_quality(state); + + // Clear previous output and redraw + printf("\033[2K\r"); // Clear line + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + printf("\033[A\033[2K\r"); // Move up and clear + + // Header + printf("Adding Entropy to Pad - Target: %zu bytes\n\n", state->target_bytes); + + // Main progress bar + printf("Progress: "); + draw_progress_bar(progress, 50); + printf(" %.1f%% (%zu/%zu bytes)\n", progress, state->collected_bytes, state->target_bytes); + + // Quality indicators + printf("Quality: "); + draw_quality_bar(quality, 50, "OVERALL"); + printf("\n"); + + printf("Timing: "); + draw_quality_bar(timing_quality, 50, "VARIED"); + printf("\n"); + + printf("Keys: "); + draw_quality_bar(variety_quality, 50, "DIVERSE"); + printf("\n"); + + // Instructions + if (state->collected_bytes >= 1024 && state->auto_complete_enabled) { + printf("\nPress ESC to finish (minimum reached) or continue typing..."); + } else if (state->collected_bytes < 1024) { + printf("\nType random keys... (%zu more bytes needed)", 1024 - state->collected_bytes); + } else { + printf("\nType random keys or press ESC when satisfied..."); + } + + fflush(stdout); +} + +// Keyboard entropy functions +int setup_raw_terminal(struct termios* original_termios) { + struct termios new_termios; + + if (tcgetattr(STDIN_FILENO, original_termios) != 0) { + return 1; + } + + new_termios = *original_termios; + new_termios.c_lflag &= ~(ICANON | ECHO); + new_termios.c_cc[VMIN] = 0; + new_termios.c_cc[VTIME] = 0; + + if (tcsetattr(STDIN_FILENO, TCSANOW, &new_termios) != 0) { + return 1; + } + + // Set stdin to non-blocking + int flags = fcntl(STDIN_FILENO, F_GETFL); + if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) == -1) { + tcsetattr(STDIN_FILENO, TCSANOW, original_termios); + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..41ae2be --- /dev/null +++ b/src/main.c @@ -0,0 +1,259 @@ +#define _POSIX_C_SOURCE 200809L +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../include/otp.h" + +int main(int argc, char* argv[]) { + // Initialize terminal dimensions first + init_terminal_dimensions(); + + // Load preferences + load_preferences(); + + // Detect interactive mode: only true when running with no arguments + set_interactive_mode((argc == 1)); + + // Check for OTP thumb drive on startup + char otp_drive_path[512]; + if (detect_otp_thumb_drive(otp_drive_path, sizeof(otp_drive_path))) { + // Only show messages in interactive mode + if (get_interactive_mode()) { + printf("Detected OTP thumb drive: %s\n", otp_drive_path); + printf("Using as default pads directory for this session.\n\n"); + } + set_current_pads_dir(otp_drive_path); + } + + if (get_interactive_mode()) { + return interactive_mode(); + } else { + return command_line_mode(argc, argv); + } +} + +int command_line_mode(int argc, char* argv[]) { + // Check for help flags first + if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--h") == 0 || + strcmp(argv[1], "-help") == 0 || strcmp(argv[1], "--help") == 0 || + strcmp(argv[1], "help") == 0) { + print_usage(argv[0]); + return 0; + } + + if (strcmp(argv[1], "generate") == 0 || strcmp(argv[1], "-g") == 0) { + if (argc != 3) { + printf("Usage: %s generate|-g \n", argv[0]); + printf("Size examples: 1024, 1GB, 5TB, 512MB\n"); + return 1; + } + uint64_t size = parse_size_string(argv[2]); + if (size == 0) { + printf("Error: Invalid size format\n"); + return 1; + } + return generate_pad(size, 1); // Use simplified pad generation + } + else if (strcmp(argv[1], "encrypt") == 0 || strcmp(argv[1], "-e") == 0) { + // Check for piped input first + if (has_stdin_data()) { + char* piped_text = read_stdin_text(); + if (piped_text) { + int result = pipe_mode(argc, argv, piped_text); + free(piped_text); + return result; + } + } + + if (argc < 2 || argc > 4) { + printf("Usage: %s encrypt|-e [pad_chksum_or_prefix] [text_to_encrypt]\n", argv[0]); + return 1; + } + + // Check if pad was specified or use default + const char* pad_identifier = NULL; + const char* text = NULL; + + if (argc == 2) { + // Just -e, use default pad, no text (interactive) + pad_identifier = NULL; + text = NULL; + } else if (argc == 3) { + // Could be -e or -e (using default pad) + // Check if default pad is available to determine interpretation + char* default_pad = get_default_pad_path(); + if (default_pad) { + // Default pad available, treat argument as text + pad_identifier = NULL; + text = argv[2]; + free(default_pad); + } else { + // No default pad, treat as pad identifier + pad_identifier = argv[2]; + text = NULL; + } + } else { + // argc == 4: -e + pad_identifier = argv[2]; + text = argv[3]; + } + + // If pad_identifier is NULL, we need to use default pad + if (pad_identifier == NULL) { + char* default_pad = get_default_pad_path(); + if (default_pad) { + // Extract checksum from default pad path + char* filename = strrchr(default_pad, '/'); + if (!filename) filename = default_pad; + else filename++; // Skip the '/' + + // Extract checksum (remove .pad extension) + if (strlen(filename) >= 68 && strstr(filename, ".pad")) { + static char default_checksum[65]; + strncpy(default_checksum, filename, 64); + default_checksum[64] = '\0'; + pad_identifier = default_checksum; + } + free(default_pad); + + // Call encrypt_text and return result + return encrypt_text(pad_identifier, text); + } else { + printf("Error: No default pad configured. Specify pad explicitly or configure default pad.\n"); + return 1; + } + } else { + // Explicit pad specified, normal operation + return encrypt_text(pad_identifier, text); + } + } + else if (strcmp(argv[1], "decrypt") == 0 || strcmp(argv[1], "-d") == 0) { + if (argc == 2) { + // Check for piped input first + if (has_stdin_data()) { + // Piped decrypt mode - read stdin and decrypt silently + char* piped_message = read_stdin_text(); + if (piped_message) { + int result = decrypt_text(NULL, piped_message); + free(piped_message); + return result; + } + } + // Interactive mode - no arguments needed + return decrypt_text(NULL, NULL); + } + else if (argc == 3) { + // Check if the argument looks like an encrypted message (starts with -----) + if (strncmp(argv[2], "-----BEGIN OTP MESSAGE-----", 27) == 0) { + // Inline decrypt with message only - use silent mode for command line + return decrypt_text(NULL, argv[2]); + } else { + // Check if it's a file (contains . or ends with known extensions) + if (strstr(argv[2], ".") != NULL) { + // Treat as file + return decrypt_file(argv[2], NULL); + } else { + // Interactive decrypt with pad hint (legacy support) + return decrypt_text(argv[2], NULL); + } + } + } + else if (argc == 4) { + // Check for -o flag for output file + if (strcmp(argv[2], "-o") == 0) { + printf("Usage: %s decrypt|-d [-o ]\n", argv[0]); + return 1; + } else { + // Legacy format: pad_chksum and message, or file with output + // Use silent mode for command line when message is provided + return decrypt_text(argv[2], argv[3]); + } + } + else if (argc == 5 && strcmp(argv[3], "-o") == 0) { + // File decryption with output: -d -o + return decrypt_file(argv[2], argv[4]); + } + else { + printf("Usage: %s decrypt|-d [encrypted_message|file] [-o output_file]\n", argv[0]); + printf(" %s decrypt|-d [encrypted_message] (pad info from message)\n", argv[0]); + return 1; + } + } + else if (strcmp(argv[1], "-f") == 0) { + // File encryption mode: -f [-a] [-o ] + if (argc < 4) { + printf("Usage: %s -f [-a] [-o ]\n", argv[0]); + return 1; + } + + const char* input_file = argv[2]; + const char* pad_prefix = argv[3]; + int ascii_armor = 0; + const char* output_file = NULL; + + // Parse optional flags + for (int i = 4; i < argc; i++) { + if (strcmp(argv[i], "-a") == 0) { + ascii_armor = 1; + } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { + output_file = argv[++i]; + } + } + + return encrypt_file(pad_prefix, input_file, output_file, ascii_armor); + } + else if (strcmp(argv[1], "list") == 0 || strcmp(argv[1], "-l") == 0) { + printf("Available pads:\n"); + char* selected = select_pad_interactive("Available pads:", "Select pad (or press Enter to exit)", PAD_FILTER_ALL, 0); + if (selected) { + free(selected); + } + return 0; + } + else { + print_usage(argv[0]); + return 1; + } +} + +void print_usage(const char* program_name) { + printf("OTP Cipher - One Time Pad Implementation v0.3.16\n"); + printf("Built for testing entropy system\n"); + printf("Usage:\n"); + printf(" %s - Interactive mode\n", program_name); + printf(" %s generate|-g - Generate new pad\n", program_name); + printf(" %s encrypt|-e [pad_checksum_prefix] [text] - Encrypt text\n", program_name); + printf(" %s decrypt|-d [encrypted_message] - Decrypt message\n", program_name); + printf(" %s -f [-a] [-o ] - Encrypt file\n", program_name); + printf(" %s list|-l - List available pads\n", program_name); + printf("\nFile Operations:\n"); + printf(" -f - Encrypt file (binary .otp format)\n"); + printf(" -f -a - Encrypt file (ASCII .otp.asc format)\n"); + printf(" -o - Specify output filename\n"); + printf("\nShort flags:\n"); + printf(" -g generate -e encrypt -d decrypt -l list -f file\n"); + printf("\nExamples:\n"); + printf(" %s -e 1a2b3c \"Hello world\" - Encrypt inline text\n", program_name); + printf(" %s -f document.pdf 1a2b - Encrypt file (binary)\n", program_name); + printf(" %s -f document.pdf 1a2b -a - Encrypt file (ASCII)\n", program_name); + printf(" %s -f document.pdf 1a2b -o secret.otp - Encrypt with custom output\n", program_name); + printf(" %s -d \"-----BEGIN OTP MESSAGE-----...\" - Decrypt message/file\n", program_name); + printf(" %s -d encrypted.otp.asc - Decrypt ASCII file\n", program_name); + printf(" %s -g 1GB - Generate 1GB pad\n", program_name); + printf(" %s -l - List pads\n", program_name); + printf("\nSize examples: 1GB, 5TB, 512MB, 2048 (bytes)\n"); + printf("Pad selection: Full chksum or prefix\n"); +} \ No newline at end of file diff --git a/src/pads.c b/src/pads.c new file mode 100644 index 0000000..865c081 --- /dev/null +++ b/src/pads.c @@ -0,0 +1,1224 @@ +#define _POSIX_C_SOURCE 200809L +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../include/otp.h" + + +// Extracted pad management functions from otp.c + +int show_pad_info(const char* chksum) { + char pad_filename[MAX_HASH_LENGTH + 10]; + char state_filename[MAX_HASH_LENGTH + 10]; + + snprintf(pad_filename, sizeof(pad_filename), "%s.pad", chksum); + snprintf(state_filename, sizeof(state_filename), "%s.state", chksum); + + struct stat st; + if (stat(pad_filename, &st) != 0) { + printf("Pad not found: %s\n", chksum); + return 1; + } + + uint64_t used_bytes; + read_state_offset(chksum, &used_bytes); + + print_centered_header("Pad Information", 0); + printf("ChkSum: %s\n", chksum); + printf("File: %s\n", pad_filename); + + double size_gb = (double)st.st_size / (1024.0 * 1024.0 * 1024.0); + double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0); + double remaining_gb = (double)(st.st_size - used_bytes) / (1024.0 * 1024.0 * 1024.0); + + printf("Total size: %.2f GB (%lu bytes)\n", size_gb, st.st_size); + printf("Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes); + printf("Remaining: %.2f GB (%lu bytes)\n", remaining_gb, st.st_size - used_bytes); + printf("Usage: %.1f%%\n", (double)used_bytes / st.st_size * 100.0); + + return 0; +} + +// Ensure pads directory exists, create if necessary +int ensure_pads_directory(void) { + const char* pads_dir = get_current_pads_dir(); + struct stat st = {0}; + if (stat(pads_dir, &st) == -1) { + if (mkdir(pads_dir, 0755) != 0) { + perror("Failed to create pads directory"); + return -1; + } + } else if (!S_ISDIR(st.st_mode)) { + fprintf(stderr, "Pads path exists but is not a directory\n"); + return -1; + } + return 0; +} + +// Construct pad and state file paths from checksum +void get_pad_path(const char* chksum, char* pad_path, char* state_path) { + const char* pads_dir = get_current_pads_dir(); + snprintf(pad_path, 1024, "%s/%s.pad", pads_dir, chksum); + snprintf(state_path, 1024, "%s/%s.state", pads_dir, chksum); +} + +int generate_pad(uint64_t size_bytes, int display_progress) { + // Ensure pads directory exists + if (ensure_pads_directory() != 0) { + printf("Error: Cannot create pads directory\n"); + return 1; + } + + char temp_filename[1024]; + char pad_path[MAX_HASH_LENGTH + 20]; + char state_path[MAX_HASH_LENGTH + 20]; + char chksum_hex[MAX_HASH_LENGTH]; + + // Create temporary filename in the pads directory to avoid cross-filesystem issues + const char* pads_dir = get_current_pads_dir(); + snprintf(temp_filename, sizeof(temp_filename), "%s/temp_%ld.pad", pads_dir, time(NULL)); + + FILE* urandom = fopen("/dev/urandom", "rb"); + if (!urandom) { + printf("Error: Cannot open /dev/urandom\n"); + return 1; + } + + FILE* pad_file = fopen(temp_filename, "wb"); + if (!pad_file) { + printf("Error: Cannot create temporary pad file %s\n", temp_filename); + fclose(urandom); + return 1; + } + + unsigned char buffer[64 * 1024]; // 64KB buffer + uint64_t bytes_written = 0; + time_t start_time = time(NULL); + + if (display_progress) { + printf("Generating pad...\n"); + } + + while (bytes_written < size_bytes) { + uint64_t chunk_size = sizeof(buffer); + if (size_bytes - bytes_written < chunk_size) { + chunk_size = size_bytes - bytes_written; + } + + if (fread(buffer, 1, (size_t)chunk_size, urandom) != (size_t)chunk_size) { + printf("Error: Failed to read from /dev/urandom\n"); + fclose(urandom); + fclose(pad_file); + unlink(temp_filename); + return 1; + } + + if (fwrite(buffer, 1, (size_t)chunk_size, pad_file) != (size_t)chunk_size) { + printf("Error: Failed to write to pad file\n"); + fclose(urandom); + fclose(pad_file); + unlink(temp_filename); + return 1; + } + + bytes_written += chunk_size; + + if (display_progress && bytes_written % PROGRESS_UPDATE_INTERVAL == 0) { + show_progress(bytes_written, size_bytes, start_time); + } + } + + if (display_progress) { + show_progress(size_bytes, size_bytes, start_time); + printf("\n"); + } + + fclose(urandom); + fclose(pad_file); + + // Calculate XOR checksum of the pad file + if (display_progress) { + printf("Calculating pad checksum...\n"); + } + if (calculate_checksum_with_progress(temp_filename, chksum_hex, display_progress, size_bytes) != 0) { + printf("Error: Cannot calculate pad checksum\n"); + unlink(temp_filename); + return 1; + } + + // Get final paths in pads directory + get_pad_path(chksum_hex, pad_path, state_path); + + // Rename temporary file to final name (atomic operation within same directory) + if (rename(temp_filename, pad_path) != 0) { + printf("Error: Cannot rename temporary pad file to final name\n"); + unlink(temp_filename); + return 1; + } + + // Set pad file to read-only + if (chmod(pad_path, S_IRUSR) != 0) { + printf("Warning: Cannot set pad file to read-only\n"); + } + + // Initialize state file with offset 32 (first 32 bytes reserved for checksum encryption) + FILE* state_file = fopen(state_path, "wb"); + if (state_file) { + uint64_t reserved_bytes = 32; + fwrite(&reserved_bytes, sizeof(uint64_t), 1, state_file); + fclose(state_file); + } else { + printf("Error: Failed to create state file\n"); + unlink(pad_path); + return 1; + } + + double size_gb = (double)size_bytes / (1024.0 * 1024.0 * 1024.0); + printf("Generated pad: %s (%.2f GB)\n", pad_path, size_gb); + printf("Pad checksum: %s\n", chksum_hex); + printf("State file: %s\n", state_path); + printf("Pad file set to read-only\n"); + printf("Use 'Add entropy' in Pads menu to enhance randomness.\n"); + + // Pause before returning to menu to let user see the success message + print_centered_header("Pad Generation Complete", 1); + + return 0; +} + +int read_state_offset(const char* pad_chksum, uint64_t* offset) { + char state_filename[1024]; + const char* pads_dir = get_current_pads_dir(); + snprintf(state_filename, sizeof(state_filename), "%s/%s.state", pads_dir, pad_chksum); + + FILE* state_file = fopen(state_filename, "rb"); + if (!state_file) { + *offset = 0; + return 0; + } + + if (fread(offset, sizeof(uint64_t), 1, state_file) != 1) { + fclose(state_file); + *offset = 0; + return 0; + } + + fclose(state_file); + return 0; +} + +int write_state_offset(const char* pad_chksum, uint64_t offset) { + char state_filename[1024]; + const char* pads_dir = get_current_pads_dir(); + snprintf(state_filename, sizeof(state_filename), "%s/%s.state", pads_dir, pad_chksum); + + FILE* state_file = fopen(state_filename, "wb"); + if (!state_file) { + return 1; + } + + if (fwrite(&offset, sizeof(uint64_t), 1, state_file) != 1) { + fclose(state_file); + return 1; + } + + fclose(state_file); + return 0; +} + +// Check if a pad is unused (0% usage) +int is_pad_unused(const char* pad_chksum) { + uint64_t used_bytes; + if (read_state_offset(pad_chksum, &used_bytes) != 0) { + return 0; // Error reading state, assume used + } + return (used_bytes <= 32); // Only reserved bytes used (32 bytes for checksum encryption) +} + +// Safely rename pad files (pad and state) from old to new checksum +int rename_pad_files_safely(const char* old_chksum, const char* new_chksum) { + char old_pad_path[1024], new_pad_path[1024]; + char old_state_path[1024], new_state_path[1024]; + const char* pads_dir = get_current_pads_dir(); + + // Construct file paths + snprintf(old_pad_path, sizeof(old_pad_path), "%s/%s.pad", pads_dir, old_chksum); + snprintf(new_pad_path, sizeof(new_pad_path), "%s/%s.pad", pads_dir, new_chksum); + snprintf(old_state_path, sizeof(old_state_path), "%s/%s.state", pads_dir, old_chksum); + snprintf(new_state_path, sizeof(new_state_path), "%s/%s.state", pads_dir, new_chksum); + + // Check if new files would conflict with existing files + if (access(new_pad_path, F_OK) == 0) { + printf("Error: New pad file already exists: %s\n", new_pad_path); + return 1; // Conflict + } + + // Rename pad file + if (rename(old_pad_path, new_pad_path) != 0) { + printf("Error: Failed to rename pad file from %s to %s\n", old_pad_path, new_pad_path); + return 2; // Pad rename failed + } + + // Rename state file (if it exists) + if (access(old_state_path, F_OK) == 0) { + if (rename(old_state_path, new_state_path) != 0) { + printf("Warning: Failed to rename state file, but pad file was renamed successfully\n"); + // Try to rollback pad file rename + rename(new_pad_path, old_pad_path); + return 3; // State rename failed + } + } + + return 0; // Success +} + +// Unified pad selection function - extracts the best UI from handle_pads_menu() +char* select_pad_interactive(const char* title, const char* prompt, pad_filter_type_t filter_type, int allow_cancel) { + // Get list of pads from current directory + const char* pads_dir = get_current_pads_dir(); + DIR* dir = opendir(pads_dir); + if (!dir) { + printf("Error: Cannot open pads directory %s\n", pads_dir); + return NULL; + } + + // Structure to store pad information + struct PadInfo { + char chksum[65]; + char size_str[32]; + char used_str[32]; + double percentage; + char location[256]; + }; + + struct PadInfo pads[100]; // Support up to 100 pads + int pad_count = 0; + + // Collect all pad information + struct dirent* entry; + while ((entry = readdir(dir)) != NULL && pad_count < 100) { + if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) { + strncpy(pads[pad_count].chksum, entry->d_name, 64); + pads[pad_count].chksum[64] = '\0'; + + // Get pad file size and usage info + char full_path[1024]; + snprintf(full_path, sizeof(full_path), "%s/%s", pads_dir, entry->d_name); + struct stat st; + if (stat(full_path, &st) == 0) { + // Get used bytes from state + uint64_t used_bytes; + read_state_offset(pads[pad_count].chksum, &used_bytes); + + // Apply filter + if (filter_type == PAD_FILTER_UNUSED_ONLY && used_bytes > 32) { + continue; // Skip used pads when filtering for unused only + } + + // Format total size + if (st.st_size < 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%luB", st.st_size); + } else if (st.st_size < 1024 * 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fKB", (double)st.st_size / 1024.0); + } else if (st.st_size < 1024 * 1024 * 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fMB", (double)st.st_size / (1024.0 * 1024.0)); + } else { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.2fGB", (double)st.st_size / (1024.0 * 1024.0 * 1024.0)); + } + + // Format used size + if (used_bytes < 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%luB", used_bytes); + } else if (used_bytes < 1024 * 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fKB", (double)used_bytes / 1024.0); + } else if (used_bytes < 1024 * 1024 * 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fMB", (double)used_bytes / (1024.0 * 1024.0)); + } else { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.2fGB", (double)used_bytes / (1024.0 * 1024.0 * 1024.0)); + } + + // Calculate percentage + pads[pad_count].percentage = (double)used_bytes / st.st_size * 100.0; + + // Set location info using directory display + get_directory_display(full_path, pads[pad_count].location, sizeof(pads[pad_count].location)); + + pad_count++; + } + } + } + closedir(dir); + + if (pad_count == 0) { + printf("\n%s\n", title); + if (filter_type == PAD_FILTER_UNUSED_ONLY) { + printf("No unused pads found.\n"); + printf("Entropy can only be added to pads with 0%% usage (only reserved bytes used).\n"); + } else { + printf("No pads found.\n"); + } + return NULL; + } + + // Calculate minimal unique prefixes for each pad + char prefixes[100][65]; + int prefix_lengths[100]; + + for (int i = 0; i < pad_count; i++) { + prefix_lengths[i] = 1; + + // Find minimal unique prefix + while (prefix_lengths[i] <= 64) { + int unique = 1; + + // Check if current prefix is unique among all other pads + for (int j = 0; j < pad_count; j++) { + if (i != j && strncmp(pads[i].chksum, pads[j].chksum, prefix_lengths[i]) == 0) { + unique = 0; + break; + } + } + + if (unique) { + break; + } + prefix_lengths[i]++; + } + + // Store the minimal prefix + strncpy(prefixes[i], pads[i].chksum, prefix_lengths[i]); + prefixes[i][prefix_lengths[i]] = '\0'; + } + + // Display title and pads table + printf("\n%s\n", title); + printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "ChkSum", "D", "Dir", "Size", "Used", "% Used"); + printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "--------", "--", "------------", "----------", "----------", "------"); + + // Get current default pad path for comparison + char* current_default = get_default_pad_path(); + char default_pad_checksum[65] = ""; + + if (current_default) { + // Extract checksum from default pad path + char* filename = strrchr(current_default, '/'); + if (!filename) filename = current_default; + else filename++; // Skip the '/' + + // Extract checksum (remove .pad extension) + if (strlen(filename) >= 68 && strstr(filename, ".pad")) { + strncpy(default_pad_checksum, filename, 64); + default_pad_checksum[64] = '\0'; + } + free(current_default); + } + + for (int i = 0; i < pad_count; i++) { + // Check if this is the default pad + int is_default = (strlen(default_pad_checksum) > 0 && + strncmp(pads[i].chksum, default_pad_checksum, 64) == 0); + + // Display first 8 characters of checksum with prefix underlined + char checksum_8char[9]; + strncpy(checksum_8char, pads[i].chksum, 8); + checksum_8char[8] = '\0'; + + printf("\033[4m%.*s\033[0m%s %-2s %-12s %-12s %-12s %.1f%%\n", + prefix_lengths[i], checksum_8char, // Underlined prefix + checksum_8char + prefix_lengths[i], // Rest of 8-char checksum + is_default ? "*" : "", // Default indicator + pads[i].location, + pads[i].size_str, + pads[i].used_str, + pads[i].percentage); + } + + // Display prompt + printf("\n%s", prompt); + if (allow_cancel) { + printf(" (or 'x' to cancel)"); + } + printf(": "); + + char input[MAX_HASH_LENGTH]; + if (!fgets(input, sizeof(input), stdin)) { + printf("Error: Failed to read input\n"); + return NULL; + } + input[strcspn(input, "\n")] = 0; + + // Handle empty input - select default pad if available + if (strlen(input) == 0) { + // Get current default pad path + char* current_default = get_default_pad_path(); + if (current_default) { + // Extract checksum from default pad path + char* filename = strrchr(current_default, '/'); + if (!filename) filename = current_default; + else filename++; // Skip the '/' + + // Extract checksum (remove .pad extension) + if (strlen(filename) >= 68 && strstr(filename, ".pad")) { + char default_checksum[65]; + strncpy(default_checksum, filename, 64); + default_checksum[64] = '\0'; + + // Verify this default pad is in our current list + for (int i = 0; i < pad_count; i++) { + if (strncmp(pads[i].chksum, default_checksum, 64) == 0) { + free(current_default); + printf("Selected default pad: %.16s...\n\n", default_checksum); + return strdup(default_checksum); + } + } + } + free(current_default); + } + // No default pad or default pad not in current list + printf("No default pad available or default pad not in current list\n"); + return NULL; + } + + // Handle cancel + if (allow_cancel && (toupper(input[0]) == 'X' && strlen(input) == 1)) { + return NULL; + } + + // Find matching pad by prefix only + int selected_pad = -1; + int match_count = 0; + + // Try prefix matching only + for (int i = 0; i < pad_count; i++) { + if (strncmp(input, pads[i].chksum, strlen(input)) == 0) { + if (match_count == 0) { + selected_pad = i; + } + match_count++; + } + } + + if (match_count == 0) { + printf("No pad found matching '%s'\n", input); + return NULL; + } else if (match_count > 1) { + printf("Ambiguous prefix. Multiple matches found.\n"); + return NULL; + } + + // Return selected pad checksum (caller must free) + return strdup(pads[selected_pad].chksum); +} + +int handle_pads_menu(void) { + printf("\n"); + print_centered_header("Pad Management", 0); + + // Get list of pads from current directory + const char* pads_dir = get_current_pads_dir(); + DIR* dir = opendir(pads_dir); + if (!dir) { + printf("Error: Cannot open pads directory %s\n", pads_dir); + return 1; + } + + // Structure to store pad information + struct PadInfo { + char chksum[65]; + char size_str[32]; + char used_str[32]; + double percentage; + char location[256]; // Store location info + }; + + struct PadInfo pads[100]; // Support up to 100 pads + int pad_count = 0; + + // Collect all pad information + struct dirent* entry; + while ((entry = readdir(dir)) != NULL && pad_count < 100) { + if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) { + strncpy(pads[pad_count].chksum, entry->d_name, 64); + pads[pad_count].chksum[64] = '\0'; + + // Get pad file size and usage info + char full_path[1024]; // Increased buffer size + snprintf(full_path, sizeof(full_path), "%s/%s", pads_dir, entry->d_name); + struct stat st; + if (stat(full_path, &st) == 0) { + // Get used bytes from state + uint64_t used_bytes; + read_state_offset(pads[pad_count].chksum, &used_bytes); + + // Format total size + if (st.st_size < 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%luB", st.st_size); + } else if (st.st_size < 1024 * 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fKB", (double)st.st_size / 1024.0); + } else if (st.st_size < 1024 * 1024 * 1024) { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fMB", (double)st.st_size / (1024.0 * 1024.0)); + } else { + snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.2fGB", (double)st.st_size / (1024.0 * 1024.0 * 1024.0)); + } + + // Format used size + if (used_bytes < 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%luB", used_bytes); + } else if (used_bytes < 1024 * 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fKB", (double)used_bytes / 1024.0); + } else if (used_bytes < 1024 * 1024 * 1024) { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fMB", (double)used_bytes / (1024.0 * 1024.0)); + } else { + snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.2fGB", (double)used_bytes / (1024.0 * 1024.0 * 1024.0)); + } + + // Calculate percentage + pads[pad_count].percentage = (double)used_bytes / st.st_size * 100.0; + + // Set location info using directory display + get_directory_display(full_path, pads[pad_count].location, sizeof(pads[pad_count].location)); + + pad_count++; + } + } + } + closedir(dir); + + if (pad_count == 0) { + printf("No pads found.\n"); + printf("\nOptions:\n"); + printf(" \033[4mG\033[0menerate new pad\n"); + printf(" E\033[4mx\033[0mit\n"); + printf("\nSelect option: "); + + char input[10]; + if (fgets(input, sizeof(input), stdin)) { + char choice = toupper(input[0]); + if (choice == 'G') { + int result = handle_generate_menu(); + if (result == 0) { + // After successful pad generation, return to pads menu + return handle_pads_menu(); + } + return result; + } + } + return 0; + } + + // Calculate minimal unique prefixes for each pad + char prefixes[100][65]; // Store the minimal prefix for each pad + int prefix_lengths[100]; // Length of minimal prefix for each pad + + for (int i = 0; i < pad_count; i++) { + prefix_lengths[i] = 1; + + // Find minimal unique prefix + while (prefix_lengths[i] <= 64) { + int unique = 1; + + // Check if current prefix is unique among all other pads + for (int j = 0; j < pad_count; j++) { + if (i != j && strncmp(pads[i].chksum, pads[j].chksum, prefix_lengths[i]) == 0) { + unique = 0; + break; + } + } + + if (unique) { + break; + } + prefix_lengths[i]++; + } + + // Store the minimal prefix + strncpy(prefixes[i], pads[i].chksum, prefix_lengths[i]); + prefixes[i][prefix_lengths[i]] = '\0'; + } + + // Display pads with minimal prefixes underlined and default indicator + printf("\nAvailable pads:\n"); + printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "ChkSum", "D", "Dir", "Size", "Used", "% Used"); + printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "--------", "--", "------------", "----------", "----------", "------"); + + // Get current default pad path for comparison + char* current_default = get_default_pad_path(); + char default_pad_checksum[65] = ""; + + if (current_default) { + // Extract checksum from default pad path + char* filename = strrchr(current_default, '/'); + if (!filename) filename = current_default; + else filename++; // Skip the '/' + + // Extract checksum (remove .pad extension) + if (strlen(filename) >= 68 && strstr(filename, ".pad")) { + strncpy(default_pad_checksum, filename, 64); + default_pad_checksum[64] = '\0'; + } + free(current_default); + } + + for (int i = 0; i < pad_count; i++) { + // Check if this is the default pad + int is_default = (strlen(default_pad_checksum) > 0 && + strncmp(pads[i].chksum, default_pad_checksum, 64) == 0); + + // Display first 8 characters of checksum with prefix underlined + char checksum_8char[9]; + strncpy(checksum_8char, pads[i].chksum, 8); + checksum_8char[8] = '\0'; + + printf("\033[4m%.*s\033[0m%s %-2s %-12s %-12s %-12s %.1f%%\n", + prefix_lengths[i], checksum_8char, // Underlined prefix + checksum_8char + prefix_lengths[i], // Rest of 8-char checksum + is_default ? "*" : "", // Default indicator + pads[i].location, // Use the stored location info + pads[i].size_str, + pads[i].used_str, + pads[i].percentage); + } + + printf("\nActions:\n"); + printf(" \033[4mG\033[0menerate new pad\n"); + printf(" \033[4mA\033[0mdd entropy to pad\n"); + printf(" \033[4mV\033[0merify pad integrity\n"); + printf(" \033[4mD\033[0melete pad\n"); + printf(" \033[4mS\033[0met default pad\n"); + printf(" E\033[4mx\033[0mit\n"); + printf("\nSelect action: "); + + char input[MAX_HASH_LENGTH]; + if (!fgets(input, sizeof(input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + input[strcspn(input, "\n")] = 0; + + // Handle actions + if (toupper(input[0]) == 'G') { + int result = handle_generate_menu(); + if (result == 0) { + // After successful pad generation, return to pads menu + return handle_pads_menu(); + } + return result; + } else if (toupper(input[0]) == 'A') { + // Add entropy to pad - use unified function with unused pads filter + char* selected_pad = select_pad_interactive("Select Unused Pad for Entropy Addition", + "Select unused pad (by prefix)", + PAD_FILTER_UNUSED_ONLY, 1); + if (!selected_pad) { + printf("Entropy addition cancelled.\n"); + return handle_pads_menu(); + } + + // Add entropy to the selected unused pad + int result = handle_add_entropy_to_pad(selected_pad); + free(selected_pad); + if (result == 0) { + // Return to pads menu after successful entropy addition + return handle_pads_menu(); + } + return result; + } else if (toupper(input[0]) == 'V') { + // Verify pad integrity - use unified function + char* selected_pad = select_pad_interactive("Select Pad for Verification", + "Select pad to verify (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Pad verification cancelled.\n"); + return handle_pads_menu(); + } + + // Verify the selected pad + handle_verify_pad(selected_pad); + free(selected_pad); + return handle_pads_menu(); // Always return to pads menu after verification + } else if (toupper(input[0]) == 'D') { + // Delete pad - use unified function + char* selected_pad = select_pad_interactive("Select Pad for Deletion", + "Select pad to delete (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Pad deletion cancelled.\n"); + return handle_pads_menu(); + } + + // Delete the selected pad + handle_delete_pad(selected_pad); + free(selected_pad); + return handle_pads_menu(); // Always return to pads menu after deletion attempt + } else if (toupper(input[0]) == 'S') { + // Set default pad - use unified function + char* selected_pad = select_pad_interactive("Select Default Pad", + "Select pad to set as default (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Default pad selection cancelled.\n"); + return handle_pads_menu(); + } + + // Construct the full absolute pad path and set as default + char new_default_path[1024]; + const char* pads_dir = get_current_pads_dir(); + if (pads_dir[0] == '/') { + // Already absolute path + int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", pads_dir, selected_pad); + if (ret >= (int)sizeof(new_default_path)) { + printf("Error: Path too long for default pad setting\n"); + free(selected_pad); + return handle_pads_menu(); + } + } else { + // Relative path - make it absolute + char current_dir[512]; + if (getcwd(current_dir, sizeof(current_dir))) { + int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s/%s.pad", current_dir, pads_dir, selected_pad); + if (ret >= (int)sizeof(new_default_path)) { + // Path was truncated, fall back to relative path + int ret2 = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", pads_dir, selected_pad); + if (ret2 >= (int)sizeof(new_default_path)) { + printf("Error: Path too long for default pad setting\n"); + free(selected_pad); + return handle_pads_menu(); + } + } + } else { + // Fallback to relative path + int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", pads_dir, selected_pad); + if (ret >= (int)sizeof(new_default_path)) { + printf("Error: Path too long for default pad setting\n"); + free(selected_pad); + return handle_pads_menu(); + } + } + } + + if (set_default_pad_path(new_default_path) == 0) { + printf("Default pad set to: %.16s...\n", selected_pad); + printf("Full path: %s\n", new_default_path); + } else { + printf("Error: Failed to update default pad preference\n"); + } + + free(selected_pad); + return handle_pads_menu(); + } else if (toupper(input[0]) == 'X') { + return 0; // Exit to main menu + } else { + printf("Invalid action. Please select G, A, S, or X.\n"); + return handle_pads_menu(); + } +} + +// Update pad checksum after entropy addition +int update_pad_checksum_after_entropy(const char* old_chksum, char* new_chksum) { + char pad_path[1024]; + const char* pads_dir = get_current_pads_dir(); + snprintf(pad_path, sizeof(pad_path), "%s/%s.pad", pads_dir, old_chksum); + + // Calculate new checksum of the modified pad + if (calculate_checksum(pad_path, new_chksum) != 0) { + printf("Error: Cannot calculate new pad checksum\n"); + return 1; + } + + // Check if checksum actually changed + if (strcmp(old_chksum, new_chksum) == 0) { + printf("Warning: Pad checksum unchanged after entropy addition\n"); + return 2; // Checksum didn't change (unusual but not fatal) + } + + // Rename pad files to use new checksum + if (rename_pad_files_safely(old_chksum, new_chksum) != 0) { + return 3; // Rename failed + } + + // Update default pad preference if this was the default pad + char* current_default = get_default_pad_path(); + if (current_default) { + // Check if the old pad was the default + if (strstr(current_default, old_chksum)) { + // Update to new checksum + char new_default_path[1024]; + const char* pads_dir = get_current_pads_dir(); + snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", pads_dir, new_chksum); + + if (set_default_pad_path(new_default_path) != 0) { + printf("Warning: Failed to update default pad preference\n"); + } else { + printf("Updated default pad to new checksum: %.16s...\n", new_chksum); + } + } + free(current_default); + } + + return 0; // Success +} + +// Verify pad integrity by checking its checksum +int handle_verify_pad(const char* chksum) { + char pad_filename[MAX_HASH_LENGTH + 10]; + char state_filename[MAX_HASH_LENGTH + 10]; + char calculated_chksum[MAX_HASH_LENGTH]; + + snprintf(pad_filename, sizeof(pad_filename), "%s.pad", chksum); + snprintf(state_filename, sizeof(state_filename), "%s.state", chksum); + + struct stat st; + if (stat(pad_filename, &st) != 0) { + printf("Pad not found: %s\n", chksum); + return 1; + } + + uint64_t used_bytes; + read_state_offset(chksum, &used_bytes); + + print_centered_header("Pad Verification", 0); + printf("ChkSum: %s\n", chksum); + printf("File: %s\n", pad_filename); + + double size_gb = (double)st.st_size / (1024.0 * 1024.0 * 1024.0); + double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0); + double remaining_gb = (double)(st.st_size - used_bytes) / (1024.0 * 1024.0 * 1024.0); + + printf("Total size: %.2f GB (%lu bytes)\n", size_gb, st.st_size); + printf("Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes); + printf("Remaining: %.2f GB (%lu bytes)\n", remaining_gb, st.st_size - used_bytes); + printf("Usage: %.1f%%\n", (double)used_bytes / st.st_size * 100.0); + + // Calculate actual checksum + printf("\nCalculating checksum...\n"); + if (calculate_checksum(pad_filename, calculated_chksum) != 0) { + printf("Error: Cannot calculate pad checksum\n"); + return 1; + } + + printf("Expected checksum: %s\n", chksum); + printf("Calculated checksum: %s\n", calculated_chksum); + + if (strcmp(chksum, calculated_chksum) == 0) { + printf("\nāœ“ Pad integrity verified - checksum matches!\n"); + printf("This pad is safe to use.\n"); + return 0; + } else { + printf("\nāœ— Pad integrity check FAILED - checksum mismatch!\n"); + printf("This pad may be corrupted and should not be used.\n"); + printf("Consider regenerating this pad.\n"); + return 1; + } +} + +// Delete a pad and its associated state file +int handle_delete_pad(const char* chksum) { + char pad_filename[MAX_HASH_LENGTH + 10]; + char state_filename[MAX_HASH_LENGTH + 10]; + + snprintf(pad_filename, sizeof(pad_filename), "%s.pad", chksum); + snprintf(state_filename, sizeof(state_filename), "%s.state", chksum); + + // Check if pad exists + if (access(pad_filename, F_OK) != 0) { + printf("Pad not found: %s\n", chksum); + return 1; + } + + // Check if this is the default pad + char* current_default = get_default_pad_path(); + int is_default = 0; + if (current_default) { + if (strstr(current_default, chksum)) { + is_default = 1; + } + free(current_default); + } + + if (is_default) { + printf("Warning: This is the current default pad.\n"); + printf("Deleting it will require setting a new default pad.\n"); + } + + // Get pad info for confirmation + struct stat st; + if (stat(pad_filename, &st) == 0) { + uint64_t used_bytes; + read_state_offset(chksum, &used_bytes); + + double size_gb = (double)st.st_size / (1024.0 * 1024.0 * 1024.0); + printf("\nPad to delete:\n"); + printf("Checksum: %s\n", chksum); + printf("Size: %.2f GB\n", size_gb); + printf("Used: %.1f%%\n", (double)used_bytes / st.st_size * 100.0); + } + + // First confirmation + printf("\nAre you sure you want to delete this pad? (y/N): "); + char input[10]; + if (!fgets(input, sizeof(input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + if (toupper(input[0]) != 'Y') { + printf("Pad deletion cancelled.\n"); + return 0; + } + + // Second confirmation with checksum + printf("Type the first 8 characters of the checksum to confirm deletion: "); + if (!fgets(input, sizeof(input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + input[strcspn(input, "\n")] = 0; + + if (strlen(input) < 8 || strncmp(input, chksum, 8) != 0) { + printf("Confirmation failed. Pad deletion cancelled.\n"); + return 1; + } + + // Delete state file first + if (access(state_filename, F_OK) == 0) { + if (unlink(state_filename) != 0) { + printf("Error: Failed to delete state file %s\n", state_filename); + return 1; + } + printf("Deleted state file: %s\n", state_filename); + } + + // Delete pad file + if (unlink(pad_filename) != 0) { + printf("Error: Failed to delete pad file %s\n", pad_filename); + return 1; + } + printf("Deleted pad file: %s\n", pad_filename); + + // Clear default pad preference if this was the default + if (is_default) { + if (set_default_pad_path("") != 0) { + printf("Warning: Failed to clear default pad preference\n"); + } else { + printf("Cleared default pad preference (was this pad)\n"); + } + } + + printf("Pad deletion completed successfully.\n"); + return 0; +} + +int handle_add_entropy_to_pad(const char* pad_chksum) { + char header_text[128]; + snprintf(header_text, sizeof(header_text), "Add Entropy to Pad: %.16s...", pad_chksum); + printf("\n"); + print_centered_header(header_text, 0); + + // Present entropy source selection menu with consistent formatting + printf("Select entropy source:\n"); + printf(" \033[4mK\033[0meyboard entropy - Random typing for entropy collection\n"); + printf(" \033[4mD\033[0mice/Coins/Cards - Manual input for high-quality entropy\n"); + printf(" \033[4mH\033[0mardware RNG - Hardware random number generators\n"); + printf(" \033[4mF\033[0mile - Load entropy from binary file\n"); + printf(" \033[4mT\033[0mest RNG Speed - Test TrueRNG/SwiftRNG device performance\n"); + printf(" E\033[4mx\033[0mit\n"); + printf("\nSelect option: "); + + char source_input[10]; + if (!fgets(source_input, sizeof(source_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + char choice = toupper(source_input[0]); + entropy_source_t entropy_source; + + switch (choice) { + case 'K': + entropy_source = ENTROPY_SOURCE_KEYBOARD; + break; + case 'D': + entropy_source = ENTROPY_SOURCE_DICE; + break; + case 'H': + // Hardware RNG selected - will handle after target_bytes selection + entropy_source = ENTROPY_SOURCE_TRUERNG; + break; + case 'F': + entropy_source = ENTROPY_SOURCE_FILE; + break; + case 'T': + // Test RNG speed - this doesn't collect entropy, just tests the device + // MOVED TO src/trng.c - commented out here + // return test_truerng_speed(); + printf("Test RNG speed functionality moved to TRNG module\n"); + return 1; + case 'X': + return 0; // Exit + default: + printf("Invalid choice. Please select K, D, H, F, T, or X.\n"); + return 1; + } + + size_t target_bytes; + + // For TrueRNG, automatically use the full pad size + if (entropy_source == ENTROPY_SOURCE_TRUERNG) { + // Get the pad file size + char pad_path[1024]; + char state_path[1024]; + get_pad_path(pad_chksum, pad_path, state_path); + + struct stat pad_stat; + if (stat(pad_path, &pad_stat) != 0) { + printf("Error: Cannot get pad file size\n"); + return 1; + } + + target_bytes = (size_t)pad_stat.st_size; + printf("\nTrueRNG selected - will enhance entire pad with hardware entropy\n"); + printf("Pad size: %.2f GB (%zu bytes)\n", + (double)target_bytes / (1024.0 * 1024.0 * 1024.0), target_bytes); + } else { + // For other entropy sources, show the selection menu + printf("\nEntropy collection options:\n"); + printf(" 1. Recommended (2048 bytes) - Optimal security\n"); + printf(" 2. Minimum (1024 bytes) - Good security\n"); + printf(" 3. Maximum (4096 bytes) - Maximum security\n"); + printf(" 4. Custom amount\n"); + printf("Enter choice (1-4): "); + + char amount_input[10]; + if (!fgets(amount_input, sizeof(amount_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + target_bytes = 2048; // Default + int amount_choice = atoi(amount_input); + + switch (amount_choice) { + case 1: + target_bytes = 2048; + break; + case 2: + target_bytes = 1024; + break; + case 3: + target_bytes = 4096; + break; + case 4: + printf("Enter custom amount (512-8192 bytes): "); + char custom_input[32]; + if (!fgets(custom_input, sizeof(custom_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + size_t custom_amount = (size_t)atoi(custom_input); + if (custom_amount < 512 || custom_amount > 8192) { + printf("Error: Invalid amount. Must be between 512 and 8192 bytes.\n"); + return 1; + } + target_bytes = custom_amount; + break; + default: + target_bytes = 2048; // Default to recommended + break; + } + } + + // For TrueRNG, detect all devices and present selection menu + if (entropy_source == ENTROPY_SOURCE_TRUERNG) { + // Use streaming collection with selected device + int result = collect_truerng_entropy_streaming_from_device(NULL, pad_chksum, target_bytes, 1, 1); + + if (result != 0) { + printf("Error: TrueRNG streaming entropy collection failed\n"); + return 1; + } + + // Update checksum after entropy addition + printf("\nšŸ”„ Updating pad checksum...\n"); + char new_chksum[65]; + int checksum_result = update_pad_checksum_after_entropy(pad_chksum, new_chksum); + + if (checksum_result == 0) { + printf("āœ“ Pad checksum updated successfully\n"); + printf(" Old checksum: %.16s...\n", pad_chksum); + printf(" New checksum: %.16s...\n", new_chksum); + printf("āœ“ Pad files renamed to new checksum\n"); + } else if (checksum_result == 2) { + printf("ℹ Checksum unchanged (unusual but not an error)\n"); + } else { + printf("⚠ Warning: Checksum update failed (entropy was added successfully)\n"); + printf(" You may need to manually handle the checksum update\n"); + return 1; + } + + printf("\nšŸŽ‰ SUCCESS! Your entire pad now has enhanced randomness!\n"); + + // Use enhanced pause mechanism instead of simple getchar + print_centered_header("Pad Enhancement Complete", 1); + + return 0; + } + + // For other entropy sources or smaller amounts, use traditional approach + printf("\nCollecting %zu bytes of entropy from selected source...\n", target_bytes); + + // Allocate entropy buffer + unsigned char* entropy_buffer = malloc(MAX_ENTROPY_BUFFER); + if (!entropy_buffer) { + printf("Error: Cannot allocate entropy buffer\n"); + return 1; + } + + // Collect entropy using unified interface + size_t collected_bytes = 0; + int result = collect_entropy_by_source(entropy_source, entropy_buffer, target_bytes, &collected_bytes, 1); + + if (result != 0) { + printf("Error: Entropy collection failed\n"); + free(entropy_buffer); + return 1; + } + + if (collected_bytes < 512) { + printf("Error: Insufficient entropy collected (%zu bytes)\n", collected_bytes); + free(entropy_buffer); + return 1; + } + + printf("\nProcessing entropy and modifying pad...\n"); + + // Add entropy to pad + result = add_entropy_to_pad(pad_chksum, entropy_buffer, collected_bytes, 1); + + // Clear entropy buffer for security + memset(entropy_buffer, 0, MAX_ENTROPY_BUFFER); + free(entropy_buffer); + + if (result != 0) { + printf("Error: Failed to add entropy to pad\n"); + return 1; + } + + printf("\nšŸŽ‰ SUCCESS! Your pad now has enhanced randomness!\n"); + + // Use enhanced pause mechanism instead of simple getchar + print_centered_header("Entropy Enhancement Complete", 1); + + return 0; +} \ No newline at end of file diff --git a/src/state.c b/src/state.c new file mode 100644 index 0000000..bd660d8 --- /dev/null +++ b/src/state.c @@ -0,0 +1,45 @@ +#include +#include +#include "../include/otp.h" + +// Global state variables +static char current_pads_dir[512] = DEFAULT_PADS_DIR; +static int is_interactive_mode = 0; + +// Terminal dimensions (moved from ui.c to state.c for global access) +static int terminal_width = 80; // Default fallback width +static int terminal_height = 24; // Default fallback height + +// Getters and setters for global state + +const char* get_current_pads_dir(void) { + return current_pads_dir; +} + +void set_current_pads_dir(const char* dir) { + if (dir) { + strncpy(current_pads_dir, dir, sizeof(current_pads_dir) - 1); + current_pads_dir[sizeof(current_pads_dir) - 1] = '\0'; + } +} + +int get_interactive_mode(void) { + return is_interactive_mode; +} + +void set_interactive_mode(int mode) { + is_interactive_mode = mode; +} + +int get_terminal_width(void) { + return terminal_width; +} + +int get_terminal_height(void) { + return terminal_height; +} + +void set_terminal_dimensions(int width, int height) { + terminal_width = width; + terminal_height = height; +} \ No newline at end of file diff --git a/src/trng.c b/src/trng.c new file mode 100644 index 0000000..9c27899 --- /dev/null +++ b/src/trng.c @@ -0,0 +1,114 @@ +#define _POSIX_C_SOURCE 200809L +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../nostr_chacha20.h" +#include "../include/otp.h" + +// Basic TrueRNG entropy collection function +int collect_truerng_entropy(unsigned char* entropy_buffer, size_t target_bytes, size_t* collected_bytes, int display_progress) { + hardware_rng_device_t devices[10]; + int num_devices_found = 0; + + // Detect available TrueRNG devices + if (detect_all_hardware_rng_devices(devices, 10, &num_devices_found) != 0) { + if (display_progress) { + printf("Error: Failed to detect hardware RNG devices\n"); + } + return 1; + } + + if (num_devices_found == 0) { + if (display_progress) { + printf("No hardware RNG devices found.\n"); + printf("\nSupported devices:\n"); + printf(" - TrueRNG/SwiftRNG (PID: %s, VID: %s)\n", TRUERNG_VID, TRUERNG_PID); + printf(" - TrueRNGpro/SwiftRNGpro (PID: %s, VID: %s)\n", TRUERNGPRO_VID, TRUERNGPRO_PID); + printf(" - TrueRNGproV2/SwiftRNGproV2 (PID: %s, VID: %s)\n", TRUERNGPROV2_VID, TRUERNGPROV2_PID); + printf("\nPlease connect a TrueRNG or SwiftRNG device and try again.\n"); + } + return 1; + } + + // Use first available device + hardware_rng_device_t* selected_device = &devices[0]; + + if (display_progress) { + printf("Using device: %s\n", selected_device->friendly_name); + printf("Collecting %zu bytes of entropy...\n", target_bytes); + } + + // Collect entropy from the device + int result = collect_truerng_entropy_from_device(selected_device, entropy_buffer, target_bytes, collected_bytes, display_progress); + + if (result != 0) { + if (display_progress) { + printf("Error: Failed to collect entropy from TrueRNG device\n"); + } + return 1; + } + + if (display_progress) { + printf("āœ“ Successfully collected %zu bytes of entropy from TrueRNG device\n", *collected_bytes); + } + + return 0; +} + +// Wrapper function to match the header declaration +// Note: Full implementation moved to otp.c during modularization +// This is a placeholder that should be implemented when the full streaming +// functionality is moved to the trng module +int collect_truerng_entropy_streaming_from_device(const hardware_rng_device_t* device, const char* pad_chksum, + size_t total_bytes, int display_progress, int entropy_mode) { + // For now, return an error - full implementation needs to be moved from otp.c + (void)device; // Suppress unused parameter warning + (void)pad_chksum; + (void)total_bytes; + (void)display_progress; + (void)entropy_mode; + + fprintf(stderr, "Error: collect_truerng_entropy_streaming_from_device not yet implemented in modular version\n"); + return 1; // Error +} + +// Detect all available hardware RNG devices +int detect_all_hardware_rng_devices(hardware_rng_device_t* devices, int max_devices, int* num_devices_found) { + *num_devices_found = 0; + + // For now, return empty list - full implementation would scan /dev for TrueRNG devices + // This is a placeholder that should be implemented when the full TRNG functionality + // is moved to the trng module + + (void)devices; // Suppress unused parameter warning + (void)max_devices; + + return 0; // Success but no devices found +} + +// Collect entropy from a specific TrueRNG device +int collect_truerng_entropy_from_device(const hardware_rng_device_t* device, unsigned char* entropy_buffer, + size_t target_bytes, size_t* collected_bytes, int display_progress) { + // For now, return an error - full implementation needs to be moved from otp.c + (void)device; // Suppress unused parameter warning + (void)entropy_buffer; + (void)target_bytes; + (void)collected_bytes; + (void)display_progress; + + fprintf(stderr, "Error: collect_truerng_entropy_from_device not yet implemented in modular version\n"); + return 1; // Error +} \ No newline at end of file diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..d41f49c --- /dev/null +++ b/src/ui.c @@ -0,0 +1,503 @@ +#define _POSIX_C_SOURCE 200809L +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../include/otp.h" + +// Initialize terminal dimensions +void init_terminal_dimensions(void) { + struct winsize ws; + + // Try to get actual terminal size + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0 && ws.ws_row > 0) { + set_terminal_dimensions(ws.ws_col, ws.ws_row); + } + // If ioctl fails, keep the default values (80x24) +} + +// Print centered header with = padding, screen clearing, and optional pause +void print_centered_header(const char* text, int pause_before_clear) { + if (!text) return; + + // Phase 1: Pause if requested + if (pause_before_clear) { + printf("\nPress Enter to continue..."); + fflush(stdout); + + // Wait for Enter key + int c; + while ((c = getchar()) != '\n' && c != EOF) { + // Consume any extra characters until newline + } + } + + // Phase 2: Clear screen using terminal height + for (int i = 0; i < get_terminal_height(); i++) { + printf("\n"); + } + + // Phase 3: Display centered header (existing logic) + int text_len = strlen(text); + int available_width = get_terminal_width(); + + // Ensure minimum spacing: at least 1 space on each side + int min_required = text_len + 4; // text + " " + text + " " (spaces around text) + + if (available_width < min_required) { + // Terminal too narrow - just print the text with minimal formatting + printf("=== %s ===\n", text); + return; + } + + // Calculate padding + int total_padding = available_width - text_len - 2; // -2 for spaces around text + int left_padding = total_padding / 2; + int right_padding = total_padding - left_padding; + + // Print the header + for (int i = 0; i < left_padding; i++) { + printf("="); + } + printf(" %s ", text); + for (int i = 0; i < right_padding; i++) { + printf("="); + } + printf("\n"); +} + +// Interactive mode main loop +int interactive_mode(void) { + char input[10]; + printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); + while (1) { + show_main_menu(); + + if (!fgets(input, sizeof(input), stdin)) { + printf("Goodbye!\n"); + break; + } + + char choice = toupper(input[0]); + + switch (choice) { + case 'T': + handle_text_encrypt(); + break; + case 'F': + handle_file_encrypt(); + break; + case 'D': + handle_decrypt_menu(); + break; + case 'P': + handle_pads_menu(); + break; + case 'X': + case 'Q': + printf("Goodbye!\n"); + return 0; + default: + printf("Invalid choice. Please try again.\n"); + break; + } + } + + return 0; +} + +void show_main_menu(void) { + printf("\n"); + print_centered_header("Main Menu - OTP v0.3.16", 0); + printf("\n"); + + printf(" \033[4mT\033[0mext encrypt\n"); //TEXT ENCRYPT + printf(" \033[4mF\033[0mile encrypt\n"); //FILE ENCRYPT + printf(" \033[4mD\033[0mecrypt\n"); //DECRYPT + printf(" \033[4mP\033[0mads\n"); //PADS + printf(" E\033[4mx\033[0mit\n"); //EXIT + printf("\nSelect option: "); +} + +int handle_generate_menu(void) { + printf("\n"); + print_centered_header("Generate New Pad", 0); + printf("Enter pad size (examples: 1GB, 5TB, 512MB, 2048): "); + + char size_input[64]; + if (!fgets(size_input, sizeof(size_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + size_input[strcspn(size_input, "\n")] = 0; + uint64_t size = parse_size_string(size_input); + + if (size == 0) { + printf("Error: Invalid size format\n"); + return 1; + } + + double size_gb = (double)size / (1024.0 * 1024.0 * 1024.0); + printf("Generating %.2f GB pad...\n", size_gb); + printf("Note: Use 'Add entropy' in Pads menu to enhance randomness after creation.\n"); + + return generate_pad(size, 1); +} + +int handle_encrypt_menu(void) { + printf("\n"); + print_centered_header("Encrypt Data", 0); + + printf("Available pads:\n"); + char* selected = select_pad_interactive("Available pads:", "Select pad (or press Enter to continue)", PAD_FILTER_ALL, 0); + int pad_count = 1; // Assume at least 1 pad if function returned + if (selected) { + free(selected); + } else { + pad_count = 0; + } + if (pad_count == 0) { + printf("No pads available. Generate a pad first.\n"); + return 1; + } + + // Ask user to choose between text and file encryption + printf("\nSelect encryption type:\n"); + printf(" 1. Text message\n"); + printf(" 2. File\n"); + printf("Enter choice (1-2): "); + + char choice_input[10]; + if (!fgets(choice_input, sizeof(choice_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + int choice = atoi(choice_input); + + if (choice == 1) { + // Text encryption - use unified pad selection + char* selected_pad = select_pad_interactive("Select Pad for Text Encryption", + "Select pad (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Text encryption cancelled.\n"); + return 1; + } + + int result = encrypt_text(selected_pad, NULL); // NULL for interactive mode + free(selected_pad); + return result; + } + else if (choice == 2) { + // File encryption + printf("\nFile selection options:\n"); + printf(" 1. Type file path directly\n"); + printf(" 2. Use file manager\n"); + printf("Enter choice (1-2): "); + + char file_choice[10]; + char input_file[512]; + + if (!fgets(file_choice, sizeof(file_choice), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + if (atoi(file_choice) == 2) { + // Use file manager + if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) { + printf("Falling back to manual file path entry.\n"); + printf("Enter input file path: "); + if (!fgets(input_file, sizeof(input_file), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + input_file[strcspn(input_file, "\n")] = 0; + } + } else { + // Direct file path input + printf("Enter input file path: "); + if (!fgets(input_file, sizeof(input_file), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + input_file[strcspn(input_file, "\n")] = 0; + } + + // Check if file exists + if (access(input_file, R_OK) != 0) { + printf("Error: File '%s' not found or cannot be read\n", input_file); + return 1; + } + + // Use unified pad selection + char* selected_pad = select_pad_interactive("Select Pad for File Encryption", + "Select pad (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("File encryption cancelled.\n"); + return 1; + } + + // Ask for output format + printf("\nSelect output format:\n"); + printf(" 1. Binary (.otp) - preserves file permissions\n"); + printf(" 2. ASCII (.otp.asc) - text-safe format\n"); + printf("Enter choice (1-2): "); + + char format_input[10]; + if (!fgets(format_input, sizeof(format_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + int ascii_armor = (atoi(format_input) == 2) ? 1 : 0; + + // Generate default output filename with files directory and use enhanced input function + char default_output[1024]; // Increased size to prevent truncation warnings + char temp_default[1024]; + + // Generate base filename with appropriate extension + if (ascii_armor) { + snprintf(temp_default, sizeof(temp_default), "%s.otp.asc", input_file); + } else { + snprintf(temp_default, sizeof(temp_default), "%s.otp", input_file); + } + + // Apply files directory default path + get_default_file_path(temp_default, default_output, sizeof(default_output)); + + char output_file[512]; + if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { + printf("Error: Failed to read input\n"); + return 1; + } + + const char* output_filename = output_file; + + int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor); + free(selected_pad); + return result; + } + else { + printf("Invalid choice. Please enter 1 or 2.\n"); + return 1; + } +} + +int handle_decrypt_menu(void) { + printf("\n"); + print_centered_header("Smart Decrypt", 0); + printf("Enter encrypted data (paste ASCII armor), file path, or press Enter to browse files:\n"); + + char input_line[MAX_LINE_LENGTH]; + if (!fgets(input_line, sizeof(input_line), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + // Remove newline + input_line[strcspn(input_line, "\n")] = 0; + + if (strlen(input_line) == 0) { + // Empty input - launch file manager to browse for files + char selected_file[512]; + if (launch_file_manager(get_files_directory(), selected_file, sizeof(selected_file)) != 0) { + printf("Error: Could not launch file manager\n"); + return 1; + } + + // Generate smart default output filename with files directory and use enhanced input function + char temp_default[512]; + char default_output[512]; + strncpy(temp_default, selected_file, sizeof(temp_default) - 1); + temp_default[sizeof(temp_default) - 1] = '\0'; + + // Remove common encrypted extensions to get a better default + if (strstr(temp_default, ".otp.asc")) { + // Replace .otp.asc with original extension or no extension + char* ext_pos = strstr(temp_default, ".otp.asc"); + *ext_pos = '\0'; + } else if (strstr(temp_default, ".otp")) { + // Replace .otp with original extension or no extension + char* ext_pos = strstr(temp_default, ".otp"); + *ext_pos = '\0'; + } else { + // No recognized encrypted extension, add .decrypted suffix + strncat(temp_default, ".decrypted", sizeof(temp_default) - strlen(temp_default) - 1); + } + + // Apply files directory default path + get_default_file_path(temp_default, default_output, sizeof(default_output)); + + char output_file[512]; + if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { + printf("Error: Failed to read input\n"); + return 1; + } + + return decrypt_file(selected_file, output_file); + } + else if (strncmp(input_line, "-----BEGIN OTP MESSAGE-----", 27) == 0) { + // Looks like ASCII armor - collect the full message + char full_message[MAX_INPUT_SIZE * 4] = {0}; + strcat(full_message, input_line); + strcat(full_message, "\n"); + + printf("Continue pasting the message (end with -----END OTP MESSAGE-----):\n"); + + char line[MAX_LINE_LENGTH]; + while (fgets(line, sizeof(line), stdin)) { + strncat(full_message, line, sizeof(full_message) - strlen(full_message) - 1); + if (strstr(line, "-----END OTP MESSAGE-----")) { + break; + } + } + + return decrypt_text(NULL, full_message); + } + else { + // Check if it looks like a file path + if (access(input_line, R_OK) == 0) { + // It's a valid file - decrypt it with enhanced input for output filename + char temp_default[512]; + char default_output[512]; + strncpy(temp_default, input_line, sizeof(temp_default) - 1); + temp_default[sizeof(temp_default) - 1] = '\0'; + + // Remove common encrypted extensions to get a better default + if (strstr(temp_default, ".otp.asc")) { + // Replace .otp.asc with original extension or no extension + char* ext_pos = strstr(temp_default, ".otp.asc"); + *ext_pos = '\0'; + } else if (strstr(temp_default, ".otp")) { + // Replace .otp with original extension or no extension + char* ext_pos = strstr(temp_default, ".otp"); + *ext_pos = '\0'; + } else { + // No recognized encrypted extension, add .decrypted suffix + strncat(temp_default, ".decrypted", sizeof(temp_default) - strlen(temp_default) - 1); + } + + // Apply files directory default path + get_default_file_path(temp_default, default_output, sizeof(default_output)); + + char output_file[512]; + if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { + printf("Error: Failed to read input\n"); + return 1; + } + + return decrypt_file(input_line, output_file); + } else { + printf("Input not recognized as ASCII armor or valid file path.\n"); + return 1; + } + } +} + +int handle_text_encrypt(void) { + printf("\n"); + print_centered_header("Text Encrypt", 0); + + // Launch text editor directly + char text_buffer[MAX_INPUT_SIZE]; + if (launch_text_editor(NULL, text_buffer, sizeof(text_buffer)) != 0) { + printf("Error: Could not launch text editor\n"); + return 1; + } + + if (strlen(text_buffer) == 0) { + printf("No text entered - canceling encryption\n"); + return 1; + } + + // Use unified pad selection + char* selected_pad = select_pad_interactive("Select Pad for Text Encryption", + "Select pad (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Text encryption cancelled.\n"); + return 1; + } + + int result = encrypt_text(selected_pad, text_buffer); + free(selected_pad); + return result; +} + +int handle_file_encrypt(void) { + printf("\n"); + print_centered_header("File Encrypt", 0); + + // Launch file manager directly + char input_file[512]; + if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) { + printf("Error: Could not launch file manager\n"); + return 1; + } + + // Check if file exists + if (access(input_file, R_OK) != 0) { + printf("Error: File '%s' not found or cannot be read\n", input_file); + return 1; + } + + // Use unified pad selection + char* selected_pad = select_pad_interactive("Select Pad for File Encryption", + "Select pad (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("File encryption cancelled.\n"); + return 1; + } + + // Ask for output format + printf("\nSelect output format:\n"); + printf(" 1. Binary (.otp) - preserves file permissions\n"); + printf(" 2. ASCII (.otp.asc) - text-safe format\n"); + printf("Enter choice (1-2): "); + + char format_input[10]; + if (!fgets(format_input, sizeof(format_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + int ascii_armor = (atoi(format_input) == 2) ? 1 : 0; + + // Generate default output filename + char default_output[1024]; // Increased buffer size to prevent truncation warnings + if (ascii_armor) { + snprintf(default_output, sizeof(default_output), "%s.otp.asc", input_file); + } else { + snprintf(default_output, sizeof(default_output), "%s.otp", input_file); + } + + // Use enhanced input function for output filename + char output_file[512]; + if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { + printf("Error: Failed to read input\n"); + return 1; + } + + const char* output_filename = output_file; + + int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor); + free(selected_pad); + return result; +} \ No newline at end of file diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..a156325 --- /dev/null +++ b/src/util.c @@ -0,0 +1,1041 @@ +#define _POSIX_C_SOURCE 200809L +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../include/otp.h" + +// Global variables for preferences +extern char current_pads_dir[512]; +static char default_pad_path[1024] = ""; + +void show_progress(uint64_t current, uint64_t total, time_t start_time) { + time_t now = time(NULL); + double elapsed = difftime(now, start_time); + + if (elapsed < 1.0) elapsed = 1.0; // Avoid division by zero + + double percentage = (double)current / total * 100.0; + double speed = (double)current / elapsed / (1024.0 * 1024.0); // MB/s + + uint64_t remaining_bytes = total - current; + double eta = remaining_bytes / (current / elapsed); + + printf("\rProgress: %.1f%% (%.1f MB/s, ETA: %.0fs) ", percentage, speed, eta); + fflush(stdout); +} + +const char* get_files_directory(void) { + struct stat st = {0}; + if (stat(FILES_DIR, &st) == 0 && S_ISDIR(st.st_mode)) { + return FILES_DIR; + } + return "."; // Fall back to current directory +} + +void get_default_file_path(const char* filename, char* result_path, size_t result_size) { + const char* files_dir = get_files_directory(); + + // If filename already has a path (contains '/'), use it as-is + if (strchr(filename, '/') != NULL) { + strncpy(result_path, filename, result_size - 1); + result_path[result_size - 1] = '\0'; + return; + } + + // Otherwise, prepend the files directory + snprintf(result_path, result_size, "%s/%s", files_dir, filename); +} + +char* get_preferred_editor(void) { + // Check EDITOR environment variable first + char* editor = getenv("EDITOR"); + if (editor && strlen(editor) > 0) { + // Verify the editor exists + char command[512]; + snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", editor); + if (system(command) == 0) { + return strdup(editor); + } + } + + // Check VISUAL environment variable + editor = getenv("VISUAL"); + if (editor && strlen(editor) > 0) { + char command[512]; + snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", editor); + if (system(command) == 0) { + return strdup(editor); + } + } + + // Try common editors in order of preference + const char* common_editors[] = {"vim", "vi", "nano", "emacs", "gedit", NULL}; + for (int i = 0; common_editors[i] != NULL; i++) { + char command[512]; + snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", common_editors[i]); + if (system(command) == 0) { + return strdup(common_editors[i]); + } + } + + return NULL; // No editor found +} + +int launch_text_editor(const char* initial_content, char* result_buffer, size_t buffer_size) { + char* editor = get_preferred_editor(); + if (!editor) { + printf("Error: No text editor found. Set EDITOR environment variable or install vim/nano.\n"); + return 1; + } + + // Create temporary file + char temp_filename[64]; + snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_edit_%ld.tmp", time(NULL)); + + // Write initial content to temp file if provided + if (initial_content && strlen(initial_content) > 0) { + FILE* temp_file = fopen(temp_filename, "w"); + if (temp_file) { + fputs(initial_content, temp_file); + fclose(temp_file); + } + } else { + // Create empty temp file + FILE* temp_file = fopen(temp_filename, "w"); + if (temp_file) { + fclose(temp_file); + } + } + + // Launch editor + printf("Opening %s for text editing...\n", editor); + char command[512]; + snprintf(command, sizeof(command), "%s %s", editor, temp_filename); + + int result = system(command); + + if (result == 0) { + // Read the edited content back + FILE* temp_file = fopen(temp_filename, "r"); + if (temp_file) { + size_t bytes_read = fread(result_buffer, 1, buffer_size - 1, temp_file); + result_buffer[bytes_read] = '\0'; + + // Remove trailing newline if present + if (bytes_read > 0 && result_buffer[bytes_read - 1] == '\n') { + result_buffer[bytes_read - 1] = '\0'; + } + + fclose(temp_file); + } else { + printf("Error: Cannot read edited content\n"); + free(editor); + unlink(temp_filename); + return 1; + } + } else { + printf("Editor exited with error or was cancelled\n"); + free(editor); + unlink(temp_filename); + return 1; + } + + // Clean up + unlink(temp_filename); + free(editor); + + return 0; +} + +char* get_preferred_file_manager(void) { + // Try file managers in order of preference + const char* file_managers[] = {"ranger", "fzf", "nnn", "lf", NULL}; + + for (int i = 0; file_managers[i] != NULL; i++) { + char command[512]; + snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", file_managers[i]); + if (system(command) == 0) { + return strdup(file_managers[i]); + } + } + + return NULL; // No file manager found +} + +int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size) { + char* fm = get_preferred_file_manager(); + if (!fm) { + printf("No file manager found. Please install ranger, fzf, nnn, or lf.\n"); + printf("Falling back to manual file path entry.\n"); + return 1; // Fall back to manual entry + } + + char temp_filename[64]; + snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_file_%ld.tmp", time(NULL)); + + char command[512]; + int result = 1; + + printf("Opening %s for file selection...\n", fm); + + if (strcmp(fm, "ranger") == 0) { + snprintf(command, sizeof(command), "cd '%s' && ranger --choosefile=%s", + start_directory ? start_directory : ".", temp_filename); + } else if (strcmp(fm, "fzf") == 0) { + snprintf(command, sizeof(command), "cd '%s' && find . -type f | fzf > %s", + start_directory ? start_directory : ".", temp_filename); + } else if (strcmp(fm, "nnn") == 0) { + snprintf(command, sizeof(command), "cd '%s' && nnn -p %s", + start_directory ? start_directory : ".", temp_filename); + } else if (strcmp(fm, "lf") == 0) { + snprintf(command, sizeof(command), "cd '%s' && lf -selection-path=%s", + start_directory ? start_directory : ".", temp_filename); + } + + result = system(command); + + if (result == 0 || result == 256) { // Some file managers return 256 on success + // Read selected file from temp file + FILE* temp_file = fopen(temp_filename, "r"); + if (temp_file) { + if (fgets(selected_file, buffer_size, temp_file)) { + // Remove trailing newline + selected_file[strcspn(selected_file, "\n\r")] = 0; + + // For relative paths from fzf, make absolute if needed + if (selected_file[0] == '.' && selected_file[1] == '/') { + char current_dir[512]; + if (getcwd(current_dir, sizeof(current_dir))) { + char abs_path[1024]; + snprintf(abs_path, sizeof(abs_path), "%s/%s", current_dir, selected_file + 2); + strncpy(selected_file, abs_path, buffer_size - 1); + selected_file[buffer_size - 1] = '\0'; + } + } + + fclose(temp_file); + unlink(temp_filename); + free(fm); + return 0; // Success + } + fclose(temp_file); + } + } + + // Clean up and indicate failure + unlink(temp_filename); + free(fm); + return 1; // Fall back to manual entry +} + +// Stdin detection functions implementation +int has_stdin_data(void) { + // Check if stdin is a pipe/redirect (not a terminal) + if (!isatty(STDIN_FILENO)) { + return 1; + } + return 0; +} + +char* read_stdin_text(void) { + size_t capacity = 4096; + size_t length = 0; + char* buffer = malloc(capacity); + + if (!buffer) { + return NULL; + } + + char chunk[1024]; + while (fgets(chunk, sizeof(chunk), stdin)) { + size_t chunk_len = strlen(chunk); + + // Ensure we have enough capacity + while (length + chunk_len >= capacity) { + capacity *= 2; + char* new_buffer = realloc(buffer, capacity); + if (!new_buffer) { + free(buffer); + return NULL; + } + buffer = new_buffer; + } + + strcpy(buffer + length, chunk); + length += chunk_len; + } + + // Remove trailing newline if present + if (length > 0 && buffer[length - 1] == '\n') { + buffer[length - 1] = '\0'; + length--; + } + + // If empty, free and return NULL + if (length == 0) { + free(buffer); + return NULL; + } + + return buffer; +} + +int pipe_mode(int argc, char* argv[], const char* piped_text) { + (void)argc; // Suppress unused parameter warning + (void)argv; // Suppress unused parameter warning + + // Check if we have a default pad configured + char* default_pad = get_default_pad_path(); + if (default_pad) { + // Verify the default pad exists and extract checksum + if (access(default_pad, R_OK) == 0) { + // Extract checksum from pad filename + char* filename = strrchr(default_pad, '/'); + if (!filename) filename = default_pad; + else filename++; // Skip the '/' + + // Extract checksum (remove .pad extension) + if (strlen(filename) >= 68 && strstr(filename, ".pad")) { + char pad_checksum[65]; + strncpy(pad_checksum, filename, 64); + pad_checksum[64] = '\0'; + + free(default_pad); + + // Encrypt using the default pad (silent mode) + return encrypt_text(pad_checksum, piped_text); + } + } + + fprintf(stderr, "Error: Default pad not found or invalid: %s\n", default_pad); + free(default_pad); + return 1; + } + + fprintf(stderr, "Error: No default pad configured for pipe mode\n"); + fprintf(stderr, "Configure a default pad in ~/.otp/otp.conf\n"); + return 1; +} + +// Preferences management functions implementation +int load_preferences(void) { + char* home_dir = getenv("HOME"); + if (!home_dir) { + return 1; // No home directory + } + + char preferences_dir[1024]; + char preferences_file[2048]; // Increased buffer size to accommodate longer paths + snprintf(preferences_dir, sizeof(preferences_dir), "%s/.otp", home_dir); + snprintf(preferences_file, sizeof(preferences_file), "%s/otp.conf", preferences_dir); + + FILE* file = fopen(preferences_file, "r"); + if (!file) { + // No preferences file exists - create it and set first pad as default + + // Create .otp directory if it doesn't exist + struct stat st = {0}; + if (stat(preferences_dir, &st) == -1) { + if (mkdir(preferences_dir, 0755) != 0) { + return 1; + } + } + + // Find the first available pad to set as default + DIR* dir = opendir(current_pads_dir); + if (dir) { + struct dirent* entry; + char first_pad_path[1024]; + int found_pad = 0; + + while ((entry = readdir(dir)) != NULL && !found_pad) { + if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) { + // Found a pad file - construct full absolute path + if (current_pads_dir[0] == '/') { + // Already absolute path + int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s", current_pads_dir, entry->d_name); + if (ret >= (int)sizeof(first_pad_path)) { + // Path was truncated, skip this entry + continue; + } + } else { + // Relative path - make it absolute + char current_dir[512]; + if (getcwd(current_dir, sizeof(current_dir))) { + int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s/%s", current_dir, current_pads_dir, entry->d_name); + if (ret >= (int)sizeof(first_pad_path)) { + // Path was truncated, skip this entry + continue; + } + } else { + // Fallback to relative path + int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s", current_pads_dir, entry->d_name); + if (ret >= (int)sizeof(first_pad_path)) { + // Path was truncated, skip this entry + continue; + } + } + } + strncpy(default_pad_path, first_pad_path, sizeof(default_pad_path) - 1); + default_pad_path[sizeof(default_pad_path) - 1] = '\0'; + found_pad = 1; + } + } + closedir(dir); + + // Create the preferences file with the default pad + if (found_pad) { + save_preferences(); + } + } + + return 0; // Successfully initialized + } + + char line[1024]; + while (fgets(line, sizeof(line), file)) { + // Remove newline + line[strcspn(line, "\n")] = 0; + + // Skip empty lines and comments + if (strlen(line) == 0 || line[0] == '#') { + continue; + } + + // Parse key=value pairs + char* equals = strchr(line, '='); + if (equals) { + *equals = '\0'; + char* key = line; + char* value = equals + 1; + + // Trim whitespace + while (*key == ' ' || *key == '\t') key++; + while (*value == ' ' || *value == '\t') value++; + + if (strcmp(key, "default_pad") == 0) { + strncpy(default_pad_path, value, sizeof(default_pad_path) - 1); + default_pad_path[sizeof(default_pad_path) - 1] = '\0'; + } + } + } + + fclose(file); + return 0; +} + +char* find_pad_by_prefix(const char* prefix) { + DIR* dir = opendir(get_current_pads_dir()); + if (!dir) { + printf("Error: Cannot open pads directory %s\n", get_current_pads_dir()); + return NULL; + } + + struct dirent* entry; + char* matches[100]; // Store up to 100 matches + int match_count = 0; + + // Always try hex prefix matching first + size_t prefix_len = strlen(prefix); + while ((entry = readdir(dir)) != NULL && match_count < 100) { + // Skip . and .. entries, and only process .pad files + if (entry->d_name[0] == '.') continue; + if (!strstr(entry->d_name, ".pad")) continue; + if (strlen(entry->d_name) != 68) continue; // 64 char chksum + ".pad" + + // Compare prefix with the filename (checksum part) + if (strncmp(entry->d_name, prefix, prefix_len) == 0) { + matches[match_count] = malloc(65); + strncpy(matches[match_count], entry->d_name, 64); + matches[match_count][64] = '\0'; + match_count++; + } + } + + // Only prefix matching is supported + + closedir(dir); + + if (match_count == 0) { + printf("No pads found matching '%s'\n", prefix); + printf("Available pads:\n"); + char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0); + if (selected) { + free(selected); + } + return NULL; + } else if (match_count == 1) { + char* result = matches[0]; + return result; + } else { + printf("Multiple matches found for '%s':\n", prefix); + for (int i = 0; i < match_count; i++) { + printf(" %.16s...\n", matches[i]); + } + printf("Please be more specific with your prefix.\n"); + + // Free all matches and return NULL + for (int i = 0; i < match_count; i++) { + free(matches[i]); + } + return NULL; + } +} + +uint64_t parse_size_string(const char* size_str) { + if (!size_str) return 0; + + char* endptr; + double value = strtod(size_str, &endptr); + + if (value <= 0) return 0; + + // Skip whitespace + while (*endptr && isspace(*endptr)) endptr++; + + uint64_t multiplier = 1; + + if (*endptr) { + char unit[4]; + strncpy(unit, endptr, 3); + unit[3] = '\0'; + + // Convert to uppercase + for (int i = 0; unit[i]; i++) { + unit[i] = toupper(unit[i]); + } + + if (strcmp(unit, "K") == 0 || strcmp(unit, "KB") == 0) { + multiplier = 1024ULL; + } else if (strcmp(unit, "M") == 0 || strcmp(unit, "MB") == 0) { + multiplier = 1024ULL * 1024ULL; + } else if (strcmp(unit, "G") == 0 || strcmp(unit, "GB") == 0) { + multiplier = 1024ULL * 1024ULL * 1024ULL; + } else if (strcmp(unit, "T") == 0 || strcmp(unit, "TB") == 0) { + multiplier = 1024ULL * 1024ULL * 1024ULL * 1024ULL; + } else { + return 0; // Invalid unit + } + } else { + // No unit specified, treat as bytes + multiplier = 1; + } + + return (uint64_t)(value * multiplier); +} + +void get_directory_display(const char* file_path, char* result, size_t result_size) { + // Extract directory path from full file path + char dir_path[512]; + char* last_slash = strrchr(file_path, '/'); + + if (last_slash) { + size_t dir_len = last_slash - file_path; + if (dir_len >= sizeof(dir_path)) { + dir_len = sizeof(dir_path) - 1; + } + strncpy(dir_path, file_path, dir_len); + dir_path[dir_len] = '\0'; + } else { + // No directory separator, assume current directory + strcpy(dir_path, "."); + } + + // USB Drive Detection and Smart Shortening + char* home_dir = getenv("HOME"); + + // Check for USB/removable media mount patterns + if (strstr(dir_path, "/media/") || strstr(dir_path, "/run/media/") || strstr(dir_path, "/mnt/")) { + // Extract USB label/name + char* media_start = NULL; + if (strstr(dir_path, "/media/")) { + media_start = strstr(dir_path, "/media/"); + } else if (strstr(dir_path, "/run/media/")) { + media_start = strstr(dir_path, "/run/media/"); + } else if (strstr(dir_path, "/mnt/")) { + media_start = strstr(dir_path, "/mnt/"); + } + + if (media_start) { + // Find the USB label part + char* path_after_media = strchr(media_start + 1, '/'); + if (path_after_media) { + path_after_media++; // Skip the slash + + // For /media/user/LABEL pattern, skip the username to get to the drive label + if (strstr(media_start, "/media/")) { + char* next_slash = strchr(path_after_media, '/'); + if (next_slash) { + path_after_media = next_slash + 1; + } + } + // For /run/media/user/LABEL pattern, skip the username + else if (strstr(media_start, "/run/media/")) { + char* next_slash = strchr(path_after_media, '/'); + if (next_slash) { + path_after_media = next_slash + 1; + } + } + + // Extract just the USB label (up to next slash or end) + char* label_end = strchr(path_after_media, '/'); + char usb_label[32]; + if (label_end) { + size_t label_len = label_end - path_after_media; + if (label_len > sizeof(usb_label) - 1) label_len = sizeof(usb_label) - 1; + strncpy(usb_label, path_after_media, label_len); + usb_label[label_len] = '\0'; + } else { + // USB label is the last part + strncpy(usb_label, path_after_media, sizeof(usb_label) - 1); + usb_label[sizeof(usb_label) - 1] = '\0'; + } + + // Format with USB: prefix, limiting total length to fit in result + snprintf(result, result_size, "USB:%s", usb_label); + // Truncate if too long + if (strlen(result) > 11) { + result[11] = '\0'; + } + return; + } + } + } + + // Home directory shortening + if (home_dir && strncmp(dir_path, home_dir, strlen(home_dir)) == 0) { + if (dir_path[strlen(home_dir)] == '/' || dir_path[strlen(home_dir)] == '\0') { + // Replace home directory with ~ + char temp[512]; + snprintf(temp, sizeof(temp), "~%s", dir_path + strlen(home_dir)); + + // If result is too long, truncate intelligently + if (strlen(temp) > 11) { + // Show ~/...end_part + char* last_part = strrchr(temp, '/'); + if (last_part && strlen(last_part) < 8) { + snprintf(result, result_size, "~...%s", last_part); + } else { + strncpy(result, temp, 11); + result[11] = '\0'; + } + } else { + strncpy(result, temp, result_size - 1); + result[result_size - 1] = '\0'; + } + return; + } + } + + // Current working directory + if (strcmp(dir_path, ".") == 0 || strcmp(dir_path, current_pads_dir) == 0) { + strncpy(result, "pads", result_size - 1); + result[result_size - 1] = '\0'; + return; + } + + // System/other paths - smart truncation with ellipsis + if (strlen(dir_path) > 11) { + // Try to show the most meaningful part + char* last_part = strrchr(dir_path, '/'); + if (last_part && strlen(last_part) < 9) { + // Show .../last_part + snprintf(result, result_size, "...%s", last_part); + } else { + // Show first part with ellipsis + strncpy(result, dir_path, 8); + strncpy(result + 8, "...", result_size - 8 - 1); + result[result_size - 1] = '\0'; + } + } else { + // Short enough, use as-is + strncpy(result, dir_path, result_size - 1); + result[result_size - 1] = '\0'; + } +} + +int get_filename_with_default(const char* prompt, const char* default_path, char* result, size_t result_size) { + // Find the last directory separator + char* last_slash = strrchr(default_path, '/'); + char directory[1024] = ""; + char filename[512] = ""; + + if (last_slash) { + // Extract directory path + size_t dir_len = last_slash - default_path + 1; // Include the trailing slash + if (dir_len < sizeof(directory)) { + strncpy(directory, default_path, dir_len); + directory[dir_len] = '\0'; + } + + // Extract filename + strncpy(filename, last_slash + 1, sizeof(filename) - 1); + filename[sizeof(filename) - 1] = '\0'; + } else { + // No directory separator, treat as filename only + strncpy(filename, default_path, sizeof(filename) - 1); + filename[sizeof(filename) - 1] = '\0'; + } + + // Setup terminal for raw input + struct termios orig_termios; + if (tcgetattr(STDIN_FILENO, &orig_termios) != 0) { + // Fallback to simple input if terminal control fails + printf("\n%s\n%s: ", prompt, directory); + fflush(stdout); + + char input_buffer[512]; + if (!fgets(input_buffer, sizeof(input_buffer), stdin)) { + return 1; + } + input_buffer[strcspn(input_buffer, "\n")] = 0; + + if (strlen(input_buffer) == 0) { + strncpy(result, default_path, result_size - 1); + } else { + if (strlen(directory) > 0) { + snprintf(result, result_size, "%s%s", directory, input_buffer); + } else { + strncpy(result, input_buffer, result_size - 1); + } + } + result[result_size - 1] = '\0'; + return 0; + } + + // Set up raw terminal mode + struct termios raw_termios = orig_termios; + raw_termios.c_lflag &= ~(ECHO | ICANON); + raw_termios.c_cc[VMIN] = 1; + raw_termios.c_cc[VTIME] = 0; + + if (tcsetattr(STDIN_FILENO, TCSANOW, &raw_termios) != 0) { + // Fallback if terminal setup fails + printf("\n%s\n%s: ", prompt, directory); + fflush(stdout); + + char input_buffer[512]; + if (!fgets(input_buffer, sizeof(input_buffer), stdin)) { + return 1; + } + input_buffer[strcspn(input_buffer, "\n")] = 0; + + if (strlen(input_buffer) == 0) { + strncpy(result, default_path, result_size - 1); + } else { + if (strlen(directory) > 0) { + snprintf(result, result_size, "%s%s", directory, input_buffer); + } else { + strncpy(result, input_buffer, result_size - 1); + } + } + result[result_size - 1] = '\0'; + return 0; + } + + // Display prompt and directory + printf("\n%s\n%s", prompt, directory); + fflush(stdout); + + // Initialize editing buffer with default filename + char edit_buffer[512]; + strncpy(edit_buffer, filename, sizeof(edit_buffer) - 1); + edit_buffer[sizeof(edit_buffer) - 1] = '\0'; + int cursor_pos = strlen(edit_buffer); + int buffer_len = cursor_pos; + + // Display initial filename + printf("%s", edit_buffer); + fflush(stdout); + + // Main editing loop + int c; + while ((c = getchar()) != EOF) { + if (c == '\n' || c == '\r') { + // Enter key - accept input + break; + } else if (c == 127 || c == 8) { + // Backspace/Delete + if (cursor_pos > 0) { + // Move everything after cursor one position left + memmove(&edit_buffer[cursor_pos - 1], &edit_buffer[cursor_pos], buffer_len - cursor_pos + 1); + cursor_pos--; + buffer_len--; + + // Redraw line: move cursor to start of filename area, clear to end, redraw buffer + printf("\r%s\033[K%s", directory, edit_buffer); + + // Position cursor correctly + if (cursor_pos < buffer_len) { + printf("\033[%dD", buffer_len - cursor_pos); + } + fflush(stdout); + } + } else if (c == 27) { + // Escape sequence (arrow keys, etc.) + int c1 = getchar(); + int c2 = getchar(); + if (c1 == '[') { + if (c2 == 'C' && cursor_pos < buffer_len) { + // Right arrow + cursor_pos++; + printf("\033[1C"); + fflush(stdout); + } else if (c2 == 'D' && cursor_pos > 0) { + // Left arrow + cursor_pos--; + printf("\033[1D"); + fflush(stdout); + } else if (c2 == 'H') { + // Home key + printf("\033[%dD", cursor_pos); + cursor_pos = 0; + fflush(stdout); + } else if (c2 == 'F') { + // End key + printf("\033[%dC", buffer_len - cursor_pos); + cursor_pos = buffer_len; + fflush(stdout); + } + } + } else if (c >= 32 && c <= 126) { + // Printable character + if (buffer_len < (int)sizeof(edit_buffer) - 1) { + // Move everything after cursor one position right + memmove(&edit_buffer[cursor_pos + 1], &edit_buffer[cursor_pos], buffer_len - cursor_pos + 1); + edit_buffer[cursor_pos] = c; + cursor_pos++; + buffer_len++; + + // Redraw from cursor position + printf("%c", c); + if (cursor_pos < buffer_len) { + // Print remaining characters and move cursor back + printf("%s\033[%dD", &edit_buffer[cursor_pos], buffer_len - cursor_pos); + } + fflush(stdout); + } + } + // Ignore other control characters + } + + // Restore terminal + tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); + printf("\n"); + + // Construct final result + if (buffer_len == 0) { + // Empty input, use default + strncpy(result, default_path, result_size - 1); + } else { + // Combine directory with edited filename + edit_buffer[buffer_len] = '\0'; + if (strlen(directory) > 0) { + snprintf(result, result_size, "%s%s", directory, edit_buffer); + } else { + strncpy(result, edit_buffer, result_size - 1); + } + } + result[result_size - 1] = '\0'; + + return 0; +} + +char* get_preference(const char* key) { + if (strcmp(key, "default_pad") == 0) { + if (strlen(default_pad_path) > 0) { + return strdup(default_pad_path); + } + } + return NULL; +} + +int set_preference(const char* key, const char* value) { + if (strcmp(key, "default_pad") == 0) { + if (value) { + strncpy(default_pad_path, value, sizeof(default_pad_path) - 1); + default_pad_path[sizeof(default_pad_path) - 1] = '\0'; + } else { + default_pad_path[0] = '\0'; + } + return save_preferences(); + } + return 1; +} + +char* get_default_pad_path(void) { + if (strlen(default_pad_path) > 0) { + return strdup(default_pad_path); + } + return NULL; +} + +int set_default_pad_path(const char* pad_path) { + if (!pad_path) { + return set_preference("default_pad", NULL); + } + + // Ensure we store the full absolute path + char absolute_path[1024]; + if (pad_path[0] == '/') { + // Already absolute path + strncpy(absolute_path, pad_path, sizeof(absolute_path) - 1); + absolute_path[sizeof(absolute_path) - 1] = '\0'; + } else { + // Relative path - make it absolute + char current_dir[512]; + if (getcwd(current_dir, sizeof(current_dir))) { + snprintf(absolute_path, sizeof(absolute_path), "%s/%s", current_dir, pad_path); + } else { + // Fallback to using the path as-is if getcwd fails + strncpy(absolute_path, pad_path, sizeof(absolute_path) - 1); + absolute_path[sizeof(absolute_path) - 1] = '\0'; + } + } + + return set_preference("default_pad", absolute_path); +} + +// OTP thumb drive detection function implementation +int detect_otp_thumb_drive(char* otp_drive_path, size_t path_size) { + const char* mount_dirs[] = {"/media", "/run/media", "/mnt", NULL}; + + for (int mount_idx = 0; mount_dirs[mount_idx] != NULL; mount_idx++) { + DIR* mount_dir = opendir(mount_dirs[mount_idx]); + if (!mount_dir) continue; + + struct dirent* mount_entry; + while ((mount_entry = readdir(mount_dir)) != NULL) { + if (mount_entry->d_name[0] == '.') continue; + + char mount_path[1024]; // Increased buffer size + snprintf(mount_path, sizeof(mount_path), "%s/%s", mount_dirs[mount_idx], mount_entry->d_name); + + // For /media, we need to go one level deeper (user directories) + if (strcmp(mount_dirs[mount_idx], "/media") == 0) { + // This is /media/[username] - look inside for drives + DIR* user_dir = opendir(mount_path); + if (!user_dir) continue; + + struct dirent* user_entry; + while ((user_entry = readdir(user_dir)) != NULL) { + if (user_entry->d_name[0] == '.') continue; + + // Check if drive name starts with "OTP" + if (strncmp(user_entry->d_name, "OTP", 3) != 0) continue; + + char user_mount_path[2048]; // Increased buffer size + // Verify buffer has enough space before concatenation + size_t mount_len = strlen(mount_path); + size_t entry_len = strlen(user_entry->d_name); + if (mount_len + entry_len + 2 < sizeof(user_mount_path)) { + snprintf(user_mount_path, sizeof(user_mount_path), "%s/%s", mount_path, user_entry->d_name); + + // Check if this is a readable directory + DIR* drive_dir = opendir(user_mount_path); + if (drive_dir) { + closedir(drive_dir); + strncpy(otp_drive_path, user_mount_path, path_size - 1); + otp_drive_path[path_size - 1] = '\0'; + closedir(user_dir); + closedir(mount_dir); + return 1; // Found OTP drive + } + } + } + closedir(user_dir); + } else if (strcmp(mount_dirs[mount_idx], "/run/media") == 0) { + // For /run/media, we need to go one level deeper (skip username) + DIR* user_dir = opendir(mount_path); + if (!user_dir) continue; + + struct dirent* user_entry; + while ((user_entry = readdir(user_dir)) != NULL) { + if (user_entry->d_name[0] == '.') continue; + + // Check if drive name starts with "OTP" + if (strncmp(user_entry->d_name, "OTP", 3) != 0) continue; + + char user_mount_path[2048]; // Increased buffer size + snprintf(user_mount_path, sizeof(user_mount_path), "%s/%s", mount_path, user_entry->d_name); + + // Check if this is a readable directory + DIR* drive_dir = opendir(user_mount_path); + if (drive_dir) { + closedir(drive_dir); + strncpy(otp_drive_path, user_mount_path, path_size - 1); + otp_drive_path[path_size - 1] = '\0'; + closedir(user_dir); + closedir(mount_dir); + return 1; // Found OTP drive + } + } + closedir(user_dir); + } else { + // Direct mount point (like /mnt/OTP_DRIVE) + // Check if drive name starts with "OTP" + if (strncmp(mount_entry->d_name, "OTP", 3) == 0) { + DIR* drive_dir = opendir(mount_path); + if (drive_dir) { + closedir(drive_dir); + strncpy(otp_drive_path, mount_path, path_size - 1); + otp_drive_path[path_size - 1] = '\0'; + closedir(mount_dir); + return 1; // Found OTP drive + } + } + } + } + closedir(mount_dir); + } + + return 0; // No OTP drive found +} + +int save_preferences(void) { + char* home_dir = getenv("HOME"); + if (!home_dir) { + return 1; + } + + char preferences_dir[1024]; + char preferences_file[2048]; // Increased buffer size to accommodate longer paths + snprintf(preferences_dir, sizeof(preferences_dir), "%s/.otp", home_dir); + snprintf(preferences_file, sizeof(preferences_file), "%s/otp.conf", preferences_dir); + + // Create .otp directory if it doesn't exist + struct stat st = {0}; + if (stat(preferences_dir, &st) == -1) { + if (mkdir(preferences_dir, 0755) != 0) { + return 1; + } + } + + FILE* file = fopen(preferences_file, "w"); + if (!file) { + return 1; + } + + fprintf(file, "# OTP Preferences File\n"); + fprintf(file, "# This file is automatically generated and updated by the OTP program\n\n"); + + if (strlen(default_pad_path) > 0) { + fprintf(file, "default_pad=%s\n", default_pad_path); + } + + fclose(file); + return 0; +} \ No newline at end of file