From 3ff91e76812b1f5997dec065421c4bca818a983d Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Thu, 18 Dec 2025 08:45:16 -0400 Subject: [PATCH] Version v0.3.22 - Fix delete error --- .gitignore | 2 +- otp copy.c | 6278 ---------------------------------------------------- otp-arm64 | Bin 108584 -> 0 bytes otp-x86_64 | Bin 119016 -> 0 bytes src/pads.c | 30 +- 5 files changed, 19 insertions(+), 6291 deletions(-) delete mode 100644 otp copy.c delete mode 100755 otp-arm64 delete mode 100755 otp-x86_64 diff --git a/.gitignore b/.gitignore index a12cb66..87ba8f4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ Gemini.md TropicOfCancer-HenryMiller.txt .gitea_token true_rng/ - +Trash/ # Auto-generated files (none currently) diff --git a/otp copy.c b/otp copy.c deleted file mode 100644 index b6a847c..0000000 --- a/otp copy.c +++ /dev/null @@ -1,6278 +0,0 @@ -#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-arm64 b/otp-arm64 deleted file mode 100755 index 455266736eef0531d489fee6c5bd90c7c3ba9f64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108584 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 YI}q3UUELjwVe7PF{wfT_eLVgD1#K4qxBvhE diff --git a/otp-x86_64 b/otp-x86_64 deleted file mode 100755 index ea6e14a4d875eb2c97ac28f783e91f8f2efe280d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 119016 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