Files
otp/otp.c

5636 lines
200 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#define _POSIX_C_SOURCE 200809L
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/ioctl.h>
#include <dirent.h>
#include <time.h>
#include <ctype.h>
#include <termios.h>
#include <fcntl.h>
#include <math.h>
#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 <size>\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 <pad> or -e <text> (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> <text>
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 <input_file> [-o <output_file>]\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 <input_file> -o <output_file>
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 <input_file> <pad_prefix> [-a] [-o <output_file>]
if (argc < 4) {
printf("Usage: %s -f <input_file> <pad_prefix> [-a] [-o <output_file>]\n", argv[0]);
return 1;
}
const char* input_file = argv[2];
const char* pad_prefix = argv[3];
int ascii_armor = 0;
const char* output_file = NULL;
// Parse optional flags
for (int i = 4; i < argc; i++) {
if (strcmp(argv[i], "-a") == 0) {
ascii_armor = 1;
} else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
output_file = argv[++i];
}
}
return encrypt_file(pad_prefix, input_file, output_file, ascii_armor);
}
else if (strcmp(argv[1], "list") == 0 || strcmp(argv[1], "-l") == 0) {
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Select pad (or press Enter to exit)", PAD_FILTER_ALL, 0);
if (selected) {
free(selected);
}
return 0;
}
else {
print_usage(argv[0]);
return 1;
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// INTERACTIVE MODE
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int interactive_mode(void) {
char input[10];
printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
while (1) {
show_main_menu();
if (!fgets(input, sizeof(input), stdin)) {
printf("Goodbye!\n");
break;
}
char choice = toupper(input[0]);
switch (choice) {
case 'T':
handle_text_encrypt();
break;
case 'F':
handle_file_encrypt();
break;
case 'D':
handle_decrypt_menu();
break;
case 'P':
handle_pads_menu();
break;
case 'X':
case 'Q':
printf("Goodbye!\n");
return 0;
default:
printf("Invalid choice. Please try again.\n");
break;
}
}
return 0;
}
void show_main_menu(void) {
printf("\n");
print_centered_header("Main Menu - OTP v0.3.15", 0);
printf("\n");
printf(" \033[4mT\033[0mext encrypt\n"); //TEXT ENCRYPT
printf(" \033[4mF\033[0mile encrypt\n"); //FILE ENCRYPT
printf(" \033[4mD\033[0mecrypt\n"); //DECRYPT
printf(" \033[4mP\033[0mads\n"); //PADS
printf(" E\033[4mx\033[0mit\n"); //EXIT
printf("\nSelect option: ");
}
int handle_generate_menu(void) {
printf("\n");
print_centered_header("Generate New Pad", 0);
printf("Enter pad size (examples: 1GB, 5TB, 512MB, 2048): ");
char size_input[64];
if (!fgets(size_input, sizeof(size_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
size_input[strcspn(size_input, "\n")] = 0;
uint64_t size = parse_size_string(size_input);
if (size == 0) {
printf("Error: Invalid size format\n");
return 1;
}
double size_gb = (double)size / (1024.0 * 1024.0 * 1024.0);
printf("Generating %.2f GB pad...\n", size_gb);
printf("Note: Use 'Add entropy' in Pads menu to enhance randomness after creation.\n");
return generate_pad(size, 1);
}
int handle_encrypt_menu(void) {
printf("\n");
print_centered_header("Encrypt Data", 0);
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Select pad (or press Enter to continue)", PAD_FILTER_ALL, 0);
int pad_count = 1; // Assume at least 1 pad if function returned
if (selected) {
free(selected);
} else {
pad_count = 0;
}
if (pad_count == 0) {
printf("No pads available. Generate a pad first.\n");
return 1;
}
// Ask user to choose between text and file encryption
printf("\nSelect encryption type:\n");
printf(" 1. Text message\n");
printf(" 2. File\n");
printf("Enter choice (1-2): ");
char choice_input[10];
if (!fgets(choice_input, sizeof(choice_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
int choice = atoi(choice_input);
if (choice == 1) {
// Text encryption - use unified pad selection
char* selected_pad = select_pad_interactive("Select Pad for Text Encryption",
"Select pad (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("Text encryption cancelled.\n");
return 1;
}
int result = encrypt_text(selected_pad, NULL); // NULL for interactive mode
free(selected_pad);
return result;
}
else if (choice == 2) {
// File encryption
printf("\nFile selection options:\n");
printf(" 1. Type file path directly\n");
printf(" 2. Use file manager\n");
printf("Enter choice (1-2): ");
char file_choice[10];
char input_file[512];
if (!fgets(file_choice, sizeof(file_choice), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
if (atoi(file_choice) == 2) {
// Use file manager
if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) {
printf("Falling back to manual file path entry.\n");
printf("Enter input file path: ");
if (!fgets(input_file, sizeof(input_file), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
input_file[strcspn(input_file, "\n")] = 0;
}
} else {
// Direct file path input
printf("Enter input file path: ");
if (!fgets(input_file, sizeof(input_file), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
input_file[strcspn(input_file, "\n")] = 0;
}
// Check if file exists
if (access(input_file, R_OK) != 0) {
printf("Error: File '%s' not found or cannot be read\n", input_file);
return 1;
}
// Use unified pad selection
char* selected_pad = select_pad_interactive("Select Pad for File Encryption",
"Select pad (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("File encryption cancelled.\n");
return 1;
}
// Ask for output format
printf("\nSelect output format:\n");
printf(" 1. Binary (.otp) - preserves file permissions\n");
printf(" 2. ASCII (.otp.asc) - text-safe format\n");
printf("Enter choice (1-2): ");
char format_input[10];
if (!fgets(format_input, sizeof(format_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
int ascii_armor = (atoi(format_input) == 2) ? 1 : 0;
// Generate default output filename with files directory and use enhanced input function
char default_output[1024]; // Increased size to prevent truncation warnings
char temp_default[1024];
// Generate base filename with appropriate extension
if (ascii_armor) {
snprintf(temp_default, sizeof(temp_default), "%s.otp.asc", input_file);
} else {
snprintf(temp_default, sizeof(temp_default), "%s.otp", input_file);
}
// Apply files directory default path
get_default_file_path(temp_default, default_output, sizeof(default_output));
char output_file[512];
if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) {
printf("Error: Failed to read input\n");
return 1;
}
const char* output_filename = output_file;
int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor);
free(selected_pad);
return result;
}
else {
printf("Invalid choice. Please enter 1 or 2.\n");
return 1;
}
}
int handle_decrypt_menu(void) {
printf("\n");
print_centered_header("Smart Decrypt", 0);
printf("Enter encrypted data (paste ASCII armor), file path, or press Enter to browse files:\n");
char input_line[MAX_LINE_LENGTH];
if (!fgets(input_line, sizeof(input_line), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
// Remove newline
input_line[strcspn(input_line, "\n")] = 0;
if (strlen(input_line) == 0) {
// Empty input - launch file manager to browse for files
char selected_file[512];
if (launch_file_manager(get_files_directory(), selected_file, sizeof(selected_file)) != 0) {
printf("Error: Could not launch file manager\n");
return 1;
}
// Generate smart default output filename with files directory and use enhanced input function
char temp_default[512];
char default_output[512];
strncpy(temp_default, selected_file, sizeof(temp_default) - 1);
temp_default[sizeof(temp_default) - 1] = '\0';
// Remove common encrypted extensions to get a better default
if (strstr(temp_default, ".otp.asc")) {
// Replace .otp.asc with original extension or no extension
char* ext_pos = strstr(temp_default, ".otp.asc");
*ext_pos = '\0';
} else if (strstr(temp_default, ".otp")) {
// Replace .otp with original extension or no extension
char* ext_pos = strstr(temp_default, ".otp");
*ext_pos = '\0';
} else {
// No recognized encrypted extension, add .decrypted suffix
strncat(temp_default, ".decrypted", sizeof(temp_default) - strlen(temp_default) - 1);
}
// Apply files directory default path
get_default_file_path(temp_default, default_output, sizeof(default_output));
char output_file[512];
if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) {
printf("Error: Failed to read input\n");
return 1;
}
return decrypt_file(selected_file, output_file);
}
else if (strncmp(input_line, "-----BEGIN OTP MESSAGE-----", 27) == 0) {
// Looks like ASCII armor - collect the full message
char full_message[MAX_INPUT_SIZE * 4] = {0};
strcat(full_message, input_line);
strcat(full_message, "\n");
printf("Continue pasting the message (end with -----END OTP MESSAGE-----):\n");
char line[MAX_LINE_LENGTH];
while (fgets(line, sizeof(line), stdin)) {
strncat(full_message, line, sizeof(full_message) - strlen(full_message) - 1);
if (strstr(line, "-----END OTP MESSAGE-----")) {
break;
}
}
return decrypt_text(NULL, full_message);
}
else {
// Check if it looks like a file path
if (access(input_line, R_OK) == 0) {
// It's a valid file - decrypt it with enhanced input for output filename
char temp_default[512];
char default_output[512];
strncpy(temp_default, input_line, sizeof(temp_default) - 1);
temp_default[sizeof(temp_default) - 1] = '\0';
// Remove common encrypted extensions to get a better default
if (strstr(temp_default, ".otp.asc")) {
// Replace .otp.asc with original extension or no extension
char* ext_pos = strstr(temp_default, ".otp.asc");
*ext_pos = '\0';
} else if (strstr(temp_default, ".otp")) {
// Replace .otp with original extension or no extension
char* ext_pos = strstr(temp_default, ".otp");
*ext_pos = '\0';
} else {
// No recognized encrypted extension, add .decrypted suffix
strncat(temp_default, ".decrypted", sizeof(temp_default) - strlen(temp_default) - 1);
}
// Apply files directory default path
get_default_file_path(temp_default, default_output, sizeof(default_output));
char output_file[512];
if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) {
printf("Error: Failed to read input\n");
return 1;
}
return decrypt_file(input_line, output_file);
} else {
printf("Input not recognized as ASCII armor or valid file path.\n");
return 1;
}
}
}
uint64_t parse_size_string(const char* size_str) {
if (!size_str) return 0;
char* endptr;
double value = strtod(size_str, &endptr);
if (value <= 0) return 0;
// Skip whitespace
while (*endptr && isspace(*endptr)) endptr++;
uint64_t multiplier = 1;
if (*endptr) {
char unit[4];
strncpy(unit, endptr, 3);
unit[3] = '\0';
// Convert to uppercase
for (int i = 0; unit[i]; i++) {
unit[i] = toupper(unit[i]);
}
if (strcmp(unit, "K") == 0 || strcmp(unit, "KB") == 0) {
multiplier = 1024ULL;
} else if (strcmp(unit, "M") == 0 || strcmp(unit, "MB") == 0) {
multiplier = 1024ULL * 1024ULL;
} else if (strcmp(unit, "G") == 0 || strcmp(unit, "GB") == 0) {
multiplier = 1024ULL * 1024ULL * 1024ULL;
} else if (strcmp(unit, "T") == 0 || strcmp(unit, "TB") == 0) {
multiplier = 1024ULL * 1024ULL * 1024ULL * 1024ULL;
} else {
return 0; // Invalid unit
}
} else {
// No unit specified, treat as bytes
multiplier = 1;
}
return (uint64_t)(value * multiplier);
}
char* find_pad_by_prefix(const char* prefix) {
DIR* dir = opendir(current_pads_dir);
if (!dir) {
printf("Error: Cannot open pads directory %s\n", current_pads_dir);
return NULL;
}
struct dirent* entry;
char* matches[100]; // Store up to 100 matches
int match_count = 0;
// Always try hex prefix matching first
size_t prefix_len = strlen(prefix);
while ((entry = readdir(dir)) != NULL && match_count < 100) {
// Skip . and .. entries, and only process .pad files
if (entry->d_name[0] == '.') continue;
if (!strstr(entry->d_name, ".pad")) continue;
if (strlen(entry->d_name) != 68) continue; // 64 char chksum + ".pad"
// Compare prefix with the filename (checksum part)
if (strncmp(entry->d_name, prefix, prefix_len) == 0) {
matches[match_count] = malloc(65);
strncpy(matches[match_count], entry->d_name, 64);
matches[match_count][64] = '\0';
match_count++;
}
}
// Only prefix matching is supported
closedir(dir);
if (match_count == 0) {
printf("No pads found matching '%s'\n", prefix);
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0);
if (selected) {
free(selected);
}
return NULL;
} else if (match_count == 1) {
char* result = matches[0];
return result;
} else {
printf("Multiple matches found for '%s':\n", prefix);
for (int i = 0; i < match_count; i++) {
printf(" %.16s...\n", matches[i]);
}
printf("Please be more specific with your prefix.\n");
// Free all matches and return NULL
for (int i = 0; i < match_count; i++) {
free(matches[i]);
}
return NULL;
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// PADS MENU
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int show_pad_info(const char* chksum) {
char pad_filename[MAX_HASH_LENGTH + 10];
char state_filename[MAX_HASH_LENGTH + 10];
snprintf(pad_filename, sizeof(pad_filename), "%s.pad", chksum);
snprintf(state_filename, sizeof(state_filename), "%s.state", chksum);
struct stat st;
if (stat(pad_filename, &st) != 0) {
printf("Pad not found: %s\n", chksum);
return 1;
}
uint64_t used_bytes;
read_state_offset(chksum, &used_bytes);
print_centered_header("Pad Information", 0);
printf("ChkSum: %s\n", chksum);
printf("File: %s\n", pad_filename);
double size_gb = (double)st.st_size / (1024.0 * 1024.0 * 1024.0);
double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0);
double remaining_gb = (double)(st.st_size - used_bytes) / (1024.0 * 1024.0 * 1024.0);
printf("Total size: %.2f GB (%lu bytes)\n", size_gb, st.st_size);
printf("Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes);
printf("Remaining: %.2f GB (%lu bytes)\n", remaining_gb, st.st_size - used_bytes);
printf("Usage: %.1f%%\n", (double)used_bytes / st.st_size * 100.0);
return 0;
}
void show_progress(uint64_t current, uint64_t total, time_t start_time) {
time_t now = time(NULL);
double elapsed = difftime(now, start_time);
if (elapsed < 1.0) elapsed = 1.0; // Avoid division by zero
double percentage = (double)current / total * 100.0;
double speed = (double)current / elapsed / (1024.0 * 1024.0); // MB/s
uint64_t remaining_bytes = total - current;
double eta = remaining_bytes / (current / elapsed);
printf("\rProgress: %.1f%% (%.1f MB/s, ETA: %.0fs) ", percentage, speed, eta);
fflush(stdout);
}
int generate_pad(uint64_t size_bytes, int display_progress) {
// Ensure pads directory exists
if (ensure_pads_directory() != 0) {
printf("Error: Cannot create pads directory\n");
return 1;
}
char temp_filename[1024];
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
char chksum_hex[MAX_HASH_LENGTH];
// Create temporary filename in the pads directory to avoid cross-filesystem issues
snprintf(temp_filename, sizeof(temp_filename), "%s/temp_%ld.pad", current_pads_dir, time(NULL));
FILE* urandom = fopen("/dev/urandom", "rb");
if (!urandom) {
printf("Error: Cannot open /dev/urandom\n");
return 1;
}
FILE* pad_file = fopen(temp_filename, "wb");
if (!pad_file) {
printf("Error: Cannot create temporary pad file %s\n", temp_filename);
fclose(urandom);
return 1;
}
unsigned char buffer[64 * 1024]; // 64KB buffer
uint64_t bytes_written = 0;
time_t start_time = time(NULL);
if (display_progress) {
printf("Generating pad...\n");
}
while (bytes_written < size_bytes) {
uint64_t chunk_size = sizeof(buffer);
if (size_bytes - bytes_written < chunk_size) {
chunk_size = size_bytes - bytes_written;
}
if (fread(buffer, 1, (size_t)chunk_size, urandom) != (size_t)chunk_size) {
printf("Error: Failed to read from /dev/urandom\n");
fclose(urandom);
fclose(pad_file);
unlink(temp_filename);
return 1;
}
if (fwrite(buffer, 1, (size_t)chunk_size, pad_file) != (size_t)chunk_size) {
printf("Error: Failed to write to pad file\n");
fclose(urandom);
fclose(pad_file);
unlink(temp_filename);
return 1;
}
bytes_written += chunk_size;
if (display_progress && bytes_written % PROGRESS_UPDATE_INTERVAL == 0) {
show_progress(bytes_written, size_bytes, start_time);
}
}
if (display_progress) {
show_progress(size_bytes, size_bytes, start_time);
printf("\n");
}
fclose(urandom);
fclose(pad_file);
// Calculate XOR checksum of the pad file
if (display_progress) {
printf("Calculating pad checksum...\n");
}
if (calculate_checksum_with_progress(temp_filename, chksum_hex, display_progress, size_bytes) != 0) {
printf("Error: Cannot calculate pad checksum\n");
unlink(temp_filename);
return 1;
}
// Get final paths in pads directory
get_pad_path(chksum_hex, pad_path, state_path);
// Rename temporary file to final name (atomic operation within same directory)
if (rename(temp_filename, pad_path) != 0) {
printf("Error: Cannot rename temporary pad file to final name\n");
unlink(temp_filename);
return 1;
}
// Set pad file to read-only
if (chmod(pad_path, S_IRUSR) != 0) {
printf("Warning: Cannot set pad file to read-only\n");
}
// Initialize state file with offset 32 (first 32 bytes reserved for checksum encryption)
FILE* state_file = fopen(state_path, "wb");
if (state_file) {
uint64_t reserved_bytes = 32;
fwrite(&reserved_bytes, sizeof(uint64_t), 1, state_file);
fclose(state_file);
} else {
printf("Error: Failed to create state file\n");
unlink(pad_path);
return 1;
}
double size_gb = (double)size_bytes / (1024.0 * 1024.0 * 1024.0);
printf("Generated pad: %s (%.2f GB)\n", pad_path, size_gb);
printf("Pad checksum: %s\n", chksum_hex);
printf("State file: %s\n", state_path);
printf("Pad file set to read-only\n");
printf("Use 'Add entropy' in Pads menu to enhance randomness.\n");
// Pause before returning to menu to let user see the success message
print_centered_header("Pad Generation Complete", 1);
return 0;
}
// In-place pad entropy addition using Chacha20
int add_entropy_to_pad(const char* pad_chksum, const unsigned char* entropy_data,
size_t entropy_size, int display_progress) {
if (!pad_chksum || !entropy_data || entropy_size < 512) {
printf("Error: Invalid entropy data or insufficient entropy\n");
return 1;
}
// Derive Chacha20 key and nonce from entropy
unsigned char key[32], nonce[12];
if (derive_chacha20_params(entropy_data, entropy_size, key, nonce) != 0) {
printf("Error: Failed to derive Chacha20 parameters from entropy\n");
return 1;
}
// Get pad file path
char pad_path[1024];
char state_path[1024];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if pad exists and get size
struct stat pad_stat;
if (stat(pad_path, &pad_stat) != 0) {
printf("Error: Pad file not found: %s\n", pad_path);
return 1;
}
uint64_t pad_size = pad_stat.st_size;
// Open pad file for read/write
FILE* pad_file = fopen(pad_path, "r+b");
if (!pad_file) {
printf("Error: Cannot open pad file for modification: %s\n", pad_path);
printf("Note: Pad files are read-only. Temporarily changing permissions...\n");
// Try to make writable temporarily
if (chmod(pad_path, S_IRUSR | S_IWUSR) != 0) {
printf("Error: Cannot change pad file permissions\n");
return 1;
}
pad_file = fopen(pad_path, "r+b");
if (!pad_file) {
printf("Error: Still cannot open pad file for modification\n");
// Restore read-only
chmod(pad_path, S_IRUSR);
return 1;
}
}
if (display_progress) {
printf("Adding entropy to pad using Chacha20...\n");
printf("Pad size: %.2f GB (%lu bytes)\n", (double)pad_size / (1024.0*1024.0*1024.0), pad_size);
}
// Process pad in chunks
unsigned char buffer[64 * 1024]; // 64KB chunks
unsigned char keystream[64 * 1024];
uint64_t offset = 0;
uint32_t counter = 0;
time_t start_time = time(NULL);
while (offset < pad_size) {
size_t chunk_size = sizeof(buffer);
if (pad_size - offset < chunk_size) {
chunk_size = pad_size - offset;
}
// Read current pad data
if (fread(buffer, 1, chunk_size, pad_file) != chunk_size) {
printf("Error: Cannot read pad data at offset %lu\n", offset);
fclose(pad_file);
chmod(pad_path, S_IRUSR); // Restore read-only
return 1;
}
// Generate keystream for this chunk
if (chacha20_encrypt(key, counter, nonce, buffer, keystream, chunk_size) != 0) {
printf("Error: Chacha20 keystream generation failed\n");
fclose(pad_file);
chmod(pad_path, S_IRUSR);
return 1;
}
// XOR existing pad with keystream (adds entropy)
for (size_t i = 0; i < chunk_size; i++) {
buffer[i] ^= keystream[i];
}
// Seek back and write modified data
if (fseek(pad_file, offset, SEEK_SET) != 0) {
printf("Error: Cannot seek to offset %lu\n", offset);
fclose(pad_file);
chmod(pad_path, S_IRUSR);
return 1;
}
if (fwrite(buffer, 1, chunk_size, pad_file) != chunk_size) {
printf("Error: Cannot write modified pad data\n");
fclose(pad_file);
chmod(pad_path, S_IRUSR);
return 1;
}
offset += chunk_size;
counter += (chunk_size + 63) / 64; // Round up for block count
// Show progress for large pads
if (display_progress && offset % (64 * 1024 * 1024) == 0) { // Every 64MB
show_progress(offset, pad_size, start_time);
}
}
fclose(pad_file);
// Restore read-only permissions
if (chmod(pad_path, S_IRUSR) != 0) {
printf("Warning: Cannot restore pad file to read-only\n");
}
if (display_progress) {
show_progress(pad_size, pad_size, start_time);
printf("\n✓ Entropy successfully added to pad using Chacha20\n");
printf("✓ Pad integrity maintained\n");
printf("✓ %zu bytes of entropy distributed across entire pad\n", entropy_size);
printf("✓ Pad restored to read-only mode\n");
// Update checksum after entropy addition
printf("\n🔄 Updating pad checksum...\n");
char new_chksum[65];
int checksum_result = update_pad_checksum_after_entropy(pad_chksum, new_chksum);
if (checksum_result == 0) {
printf("✓ Pad checksum updated successfully\n");
printf(" Old checksum: %.16s...\n", pad_chksum);
printf(" New checksum: %.16s...\n", new_chksum);
printf("✓ Pad files renamed to new checksum\n");
// Pause before returning to menu to let user see the success message
print_centered_header("Entropy Addition Complete", 1);
} else if (checksum_result == 2) {
printf(" Checksum unchanged (unusual but not an error)\n");
} else {
printf("⚠ Warning: Checksum update failed (entropy was added successfully)\n");
printf(" You may need to manually handle the checksum update\n");
return 1; // Report error despite successful entropy addition
}
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// ENCRYPT AND DECRYPT
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int encrypt_text(const char* pad_identifier, const char* input_text) {
char* pad_chksum = find_pad_by_prefix(pad_identifier);
if (!pad_chksum) {
return 1;
}
char text_buffer[MAX_INPUT_SIZE];
char chksum_hex[MAX_HASH_LENGTH];
uint64_t current_offset;
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if pad file exists
if (access(pad_path, R_OK) != 0) {
printf("Error: Pad file %s not found\n", pad_path);
free(pad_chksum);
return 1;
}
// Read current offset
if (read_state_offset(pad_chksum, &current_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, &current_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(&current_offset, sizeof(uint64_t), 1, output_fp);
// File mode: 4 bytes
uint32_t file_mode = input_stat.st_mode;
fwrite(&file_mode, sizeof(uint32_t), 1, output_fp);
// File size: 8 bytes
fwrite(&file_size, sizeof(uint64_t), 1, output_fp);
// Encrypted data
fwrite(encrypted_data, 1, file_size, output_fp);
fclose(output_fp);
}
// Update state offset
if (write_state_offset(pad_chksum, current_offset + file_size) != 0) {
printf("Warning: Failed to update state file\n");
}
printf("File encrypted successfully: %s\n", output_file);
if (ascii_armor) {
printf("Format: ASCII armored (.otp.asc)\n");
} else {
printf("Format: Binary (.otp)\n");
}
// Pause before returning to menu to let user see the success message
print_centered_header("File Encryption Complete", 1);
// Cleanup
free(encrypted_data);
free(pad_chksum);
return 0;
}
int decrypt_file(const char* input_file, const char* output_file) {
// Check if input file exists
if (access(input_file, R_OK) != 0) {
printf("Error: Input file %s not found\n", input_file);
return 1;
}
FILE* input_fp = fopen(input_file, "rb");
if (!input_fp) {
printf("Error: Cannot open input file %s\n", input_file);
return 1;
}
// Read first few bytes to determine format
char magic[4];
if (fread(magic, 1, 4, input_fp) != 4) {
printf("Error: Cannot read file header\n");
fclose(input_fp);
return 1;
}
fseek(input_fp, 0, SEEK_SET); // Reset to beginning
if (memcmp(magic, "OTP\0", 4) == 0) {
// Binary format
return decrypt_binary_file(input_fp, output_file);
} else {
// Assume ASCII armored format, read entire file as text
fclose(input_fp);
return decrypt_ascii_file(input_file, output_file);
}
}
int decrypt_binary_file(FILE* input_fp, const char* output_file) {
// Read binary header
char magic[4];
uint16_t version;
unsigned char pad_chksum_bin[32];
uint64_t pad_offset;
uint32_t file_mode;
uint64_t file_size;
if (fread(magic, 1, 4, input_fp) != 4 ||
fread(&version, sizeof(uint16_t), 1, input_fp) != 1 ||
fread(pad_chksum_bin, 1, 32, input_fp) != 32 ||
fread(&pad_offset, sizeof(uint64_t), 1, input_fp) != 1 ||
fread(&file_mode, sizeof(uint32_t), 1, input_fp) != 1 ||
fread(&file_size, sizeof(uint64_t), 1, input_fp) != 1) {
printf("Error: Cannot read binary header\n");
fclose(input_fp);
return 1;
}
if (memcmp(magic, "OTP\0", 4) != 0) {
printf("Error: Invalid binary format\n");
fclose(input_fp);
return 1;
}
// Convert binary checksum to hex
char pad_chksum_hex[65];
for (int i = 0; i < 32; i++) {
sprintf(pad_chksum_hex + i*2, "%02x", pad_chksum_bin[i]);
}
pad_chksum_hex[64] = '\0';
printf("Decrypting binary file...\n");
printf("File size: %lu bytes\n", file_size);
// Check if we have the required pad
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
get_pad_path(pad_chksum_hex, pad_path, state_path);
if (access(pad_path, R_OK) != 0) {
printf("Error: Required pad not found: %s\n", pad_chksum_hex);
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0);
if (selected) {
free(selected);
}
fclose(input_fp);
return 1;
}
// Determine output filename
char default_output[512];
if (output_file == NULL) {
snprintf(default_output, sizeof(default_output), "decrypted.bin");
output_file = default_output;
}
// Read encrypted data
unsigned char* encrypted_data = malloc(file_size);
if (fread(encrypted_data, 1, file_size, input_fp) != file_size) {
printf("Error: Cannot read encrypted data\n");
free(encrypted_data);
fclose(input_fp);
return 1;
}
fclose(input_fp);
// Open pad file and decrypt
FILE* pad_file = fopen(pad_path, "rb");
if (!pad_file) {
printf("Error: Cannot open pad file\n");
free(encrypted_data);
return 1;
}
if (fseek(pad_file, pad_offset, SEEK_SET) != 0) {
printf("Error: Cannot seek to offset in pad file\n");
free(encrypted_data);
fclose(pad_file);
return 1;
}
unsigned char* pad_data = malloc(file_size);
if (fread(pad_data, 1, file_size, pad_file) != file_size) {
printf("Error: Cannot read pad data\n");
free(encrypted_data);
free(pad_data);
fclose(pad_file);
return 1;
}
fclose(pad_file);
// Use universal XOR operation for decryption
if (universal_xor_operation(encrypted_data, file_size, pad_data, encrypted_data) != 0) {
printf("Error: Decryption operation failed\n");
free(encrypted_data);
free(pad_data);
return 1;
}
// Write decrypted file
FILE* output_fp = fopen(output_file, "wb");
if (!output_fp) {
printf("Error: Cannot create output file %s\n", output_file);
free(encrypted_data);
free(pad_data);
return 1;
}
if (fwrite(encrypted_data, 1, file_size, output_fp) != file_size) {
printf("Error: Cannot write decrypted data\n");
free(encrypted_data);
free(pad_data);
fclose(output_fp);
return 1;
}
fclose(output_fp);
// Restore file permissions
if (chmod(output_file, file_mode) != 0) {
printf("Warning: Cannot restore file permissions\n");
}
printf("File decrypted successfully: %s\n", output_file);
printf("Restored permissions and metadata\n");
// Pause before returning to menu to let user see the success message
print_centered_header("File Decryption Complete", 1);
// Cleanup
free(encrypted_data);
free(pad_data);
return 0;
}
int decrypt_ascii_file(const char* input_file, const char* output_file) {
// Use universal decrypt function with file-to-file mode
return universal_decrypt(input_file, output_file, DECRYPT_MODE_FILE_TO_FILE);
}
int read_state_offset(const char* pad_chksum, uint64_t* offset) {
char state_filename[1024];
snprintf(state_filename, sizeof(state_filename), "%s/%s.state", current_pads_dir, pad_chksum);
FILE* state_file = fopen(state_filename, "rb");
if (!state_file) {
*offset = 0;
return 0;
}
if (fread(offset, sizeof(uint64_t), 1, state_file) != 1) {
fclose(state_file);
*offset = 0;
return 0;
}
fclose(state_file);
return 0;
}
int write_state_offset(const char* pad_chksum, uint64_t offset) {
char state_filename[1024];
snprintf(state_filename, sizeof(state_filename), "%s/%s.state", current_pads_dir, pad_chksum);
FILE* state_file = fopen(state_filename, "wb");
if (!state_file) {
return 1;
}
if (fwrite(&offset, sizeof(uint64_t), 1, state_file) != 1) {
fclose(state_file);
return 1;
}
fclose(state_file);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// DIRECTORY MANAGEMENT FUNCTIONS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int ensure_pads_directory(void) {
struct stat st = {0};
if (stat(current_pads_dir, &st) == -1) {
if (mkdir(current_pads_dir, 0755) != 0) {
return 1;
}
}
return 0;
}
const char* get_files_directory(void) {
struct stat st = {0};
if (stat(FILES_DIR, &st) == 0 && S_ISDIR(st.st_mode)) {
return FILES_DIR;
}
return "."; // Fall back to current directory
}
void get_default_file_path(const char* filename, char* result_path, size_t result_size) {
const char* files_dir = get_files_directory();
// If filename already has a path (contains '/'), use it as-is
if (strchr(filename, '/') != NULL) {
strncpy(result_path, filename, result_size - 1);
result_path[result_size - 1] = '\0';
return;
}
// Otherwise, prepend the files directory
snprintf(result_path, result_size, "%s/%s", files_dir, filename);
}
void get_directory_display(const char* file_path, char* result, size_t result_size) {
// Extract directory path from full file path
char dir_path[512];
char* last_slash = strrchr(file_path, '/');
if (last_slash) {
size_t dir_len = last_slash - file_path;
if (dir_len >= sizeof(dir_path)) {
dir_len = sizeof(dir_path) - 1;
}
strncpy(dir_path, file_path, dir_len);
dir_path[dir_len] = '\0';
} else {
// No directory separator, assume current directory
strcpy(dir_path, ".");
}
// USB Drive Detection and Smart Shortening
char* home_dir = getenv("HOME");
// Check for USB/removable media mount patterns
if (strstr(dir_path, "/media/") || strstr(dir_path, "/run/media/") || strstr(dir_path, "/mnt/")) {
// Extract USB label/name
char* media_start = NULL;
if (strstr(dir_path, "/media/")) {
media_start = strstr(dir_path, "/media/");
} else if (strstr(dir_path, "/run/media/")) {
media_start = strstr(dir_path, "/run/media/");
} else if (strstr(dir_path, "/mnt/")) {
media_start = strstr(dir_path, "/mnt/");
}
if (media_start) {
// Find the USB label part
char* path_after_media = strchr(media_start + 1, '/');
if (path_after_media) {
path_after_media++; // Skip the slash
// For /media/user/LABEL pattern, skip the username to get to the drive label
if (strstr(media_start, "/media/")) {
char* next_slash = strchr(path_after_media, '/');
if (next_slash) {
path_after_media = next_slash + 1;
}
}
// For /run/media/user/LABEL pattern, skip the username
else if (strstr(media_start, "/run/media/")) {
char* next_slash = strchr(path_after_media, '/');
if (next_slash) {
path_after_media = next_slash + 1;
}
}
// Extract just the USB label (up to next slash or end)
char* label_end = strchr(path_after_media, '/');
char usb_label[32];
if (label_end) {
size_t label_len = label_end - path_after_media;
if (label_len > sizeof(usb_label) - 1) label_len = sizeof(usb_label) - 1;
strncpy(usb_label, path_after_media, label_len);
usb_label[label_len] = '\0';
} else {
// USB label is the last part
strncpy(usb_label, path_after_media, sizeof(usb_label) - 1);
usb_label[sizeof(usb_label) - 1] = '\0';
}
// Format with USB: prefix, limiting total length to fit in result
snprintf(result, result_size, "USB:%s", usb_label);
// Truncate if too long
if (strlen(result) > 11) {
result[11] = '\0';
}
return;
}
}
}
// Home directory shortening
if (home_dir && strncmp(dir_path, home_dir, strlen(home_dir)) == 0) {
if (dir_path[strlen(home_dir)] == '/' || dir_path[strlen(home_dir)] == '\0') {
// Replace home directory with ~
char temp[512];
snprintf(temp, sizeof(temp), "~%s", dir_path + strlen(home_dir));
// If result is too long, truncate intelligently
if (strlen(temp) > 11) {
// Show ~/...end_part
char* last_part = strrchr(temp, '/');
if (last_part && strlen(last_part) < 8) {
snprintf(result, result_size, "~...%s", last_part);
} else {
strncpy(result, temp, 11);
result[11] = '\0';
}
} else {
strncpy(result, temp, result_size - 1);
result[result_size - 1] = '\0';
}
return;
}
}
// Current working directory
if (strcmp(dir_path, ".") == 0 || strcmp(dir_path, current_pads_dir) == 0) {
strncpy(result, "pads", result_size - 1);
result[result_size - 1] = '\0';
return;
}
// System/other paths - smart truncation with ellipsis
if (strlen(dir_path) > 11) {
// Try to show the most meaningful part
char* last_part = strrchr(dir_path, '/');
if (last_part && strlen(last_part) < 9) {
// Show .../last_part
snprintf(result, result_size, "...%s", last_part);
} else {
// Show first part with ellipsis
strncpy(result, dir_path, 8);
strncpy(result + 8, "...", result_size - 8 - 1);
result[result_size - 1] = '\0';
}
} else {
// Short enough, use as-is
strncpy(result, dir_path, result_size - 1);
result[result_size - 1] = '\0';
}
}
void get_pad_path(const char* chksum, char* pad_path, char* state_path) {
snprintf(pad_path, 1024, "%s/%s.pad", current_pads_dir, chksum);
snprintf(state_path, 1024, "%s/%s.state", current_pads_dir, chksum);
}
// Stdin detection functions implementation
int has_stdin_data(void) {
// Check if stdin is a pipe/redirect (not a terminal)
if (!isatty(STDIN_FILENO)) {
return 1;
}
return 0;
}
char* read_stdin_text(void) {
size_t capacity = 4096;
size_t length = 0;
char* buffer = malloc(capacity);
if (!buffer) {
return NULL;
}
char chunk[1024];
while (fgets(chunk, sizeof(chunk), stdin)) {
size_t chunk_len = strlen(chunk);
// Ensure we have enough capacity
while (length + chunk_len >= capacity) {
capacity *= 2;
char* new_buffer = realloc(buffer, capacity);
if (!new_buffer) {
free(buffer);
return NULL;
}
buffer = new_buffer;
}
strcpy(buffer + length, chunk);
length += chunk_len;
}
// Remove trailing newline if present
if (length > 0 && buffer[length - 1] == '\n') {
buffer[length - 1] = '\0';
length--;
}
// If empty, free and return NULL
if (length == 0) {
free(buffer);
return NULL;
}
return buffer;
}
int pipe_mode(int argc, char* argv[], const char* piped_text) {
(void)argc; // Suppress unused parameter warning
(void)argv; // Suppress unused parameter warning
// Check if we have a default pad configured
char* default_pad = get_default_pad_path();
if (default_pad) {
// Verify the default pad exists and extract checksum
if (access(default_pad, R_OK) == 0) {
// Extract checksum from pad filename
char* filename = strrchr(default_pad, '/');
if (!filename) filename = default_pad;
else filename++; // Skip the '/'
// Extract checksum (remove .pad extension)
if (strlen(filename) >= 68 && strstr(filename, ".pad")) {
char pad_checksum[65];
strncpy(pad_checksum, filename, 64);
pad_checksum[64] = '\0';
free(default_pad);
// Encrypt using the default pad (silent mode)
return encrypt_text(pad_checksum, piped_text);
}
}
fprintf(stderr, "Error: Default pad not found or invalid: %s\n", default_pad);
free(default_pad);
return 1;
}
fprintf(stderr, "Error: No default pad configured for pipe mode\n");
fprintf(stderr, "Configure a default pad in ~/.otp/otp.conf\n");
return 1;
}
// Preferences management functions implementation
int load_preferences(void) {
char* home_dir = getenv("HOME");
if (!home_dir) {
return 1; // No home directory
}
char preferences_dir[1024];
char preferences_file[2048]; // Increased buffer size to accommodate longer paths
snprintf(preferences_dir, sizeof(preferences_dir), "%s/.otp", home_dir);
snprintf(preferences_file, sizeof(preferences_file), "%s/otp.conf", preferences_dir);
FILE* file = fopen(preferences_file, "r");
if (!file) {
// No preferences file exists - create it and set first pad as default
// Create .otp directory if it doesn't exist
struct stat st = {0};
if (stat(preferences_dir, &st) == -1) {
if (mkdir(preferences_dir, 0755) != 0) {
return 1;
}
}
// Find the first available pad to set as default
DIR* dir = opendir(current_pads_dir);
if (dir) {
struct dirent* entry;
char first_pad_path[1024];
int found_pad = 0;
while ((entry = readdir(dir)) != NULL && !found_pad) {
if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) {
// Found a pad file - construct full absolute path
if (current_pads_dir[0] == '/') {
// Already absolute path
int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s", current_pads_dir, entry->d_name);
if (ret >= (int)sizeof(first_pad_path)) {
// Path was truncated, skip this entry
continue;
}
} else {
// Relative path - make it absolute
char current_dir[512];
if (getcwd(current_dir, sizeof(current_dir))) {
int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s/%s", current_dir, current_pads_dir, entry->d_name);
if (ret >= (int)sizeof(first_pad_path)) {
// Path was truncated, skip this entry
continue;
}
} else {
// Fallback to relative path
int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s", current_pads_dir, entry->d_name);
if (ret >= (int)sizeof(first_pad_path)) {
// Path was truncated, skip this entry
continue;
}
}
}
strncpy(default_pad_path, first_pad_path, sizeof(default_pad_path) - 1);
default_pad_path[sizeof(default_pad_path) - 1] = '\0';
found_pad = 1;
}
}
closedir(dir);
// Create the preferences file with the default pad
if (found_pad) {
save_preferences();
}
}
return 0; // Successfully initialized
}
char line[1024];
while (fgets(line, sizeof(line), file)) {
// Remove newline
line[strcspn(line, "\n")] = 0;
// Skip empty lines and comments
if (strlen(line) == 0 || line[0] == '#') {
continue;
}
// Parse key=value pairs
char* equals = strchr(line, '=');
if (equals) {
*equals = '\0';
char* key = line;
char* value = equals + 1;
// Trim whitespace
while (*key == ' ' || *key == '\t') key++;
while (*value == ' ' || *value == '\t') value++;
if (strcmp(key, "default_pad") == 0) {
strncpy(default_pad_path, value, sizeof(default_pad_path) - 1);
default_pad_path[sizeof(default_pad_path) - 1] = '\0';
}
}
}
fclose(file);
return 0;
}
int save_preferences(void) {
char* home_dir = getenv("HOME");
if (!home_dir) {
return 1;
}
char preferences_dir[1024];
char preferences_file[2048]; // Increased buffer size to accommodate longer paths
snprintf(preferences_dir, sizeof(preferences_dir), "%s/.otp", home_dir);
snprintf(preferences_file, sizeof(preferences_file), "%s/otp.conf", preferences_dir);
// Create .otp directory if it doesn't exist
struct stat st = {0};
if (stat(preferences_dir, &st) == -1) {
if (mkdir(preferences_dir, 0755) != 0) {
return 1;
}
}
FILE* file = fopen(preferences_file, "w");
if (!file) {
return 1;
}
fprintf(file, "# OTP Preferences File\n");
fprintf(file, "# This file is automatically generated and updated by the OTP program\n\n");
if (strlen(default_pad_path) > 0) {
fprintf(file, "default_pad=%s\n", default_pad_path);
}
fclose(file);
return 0;
}
char* get_preference(const char* key) {
if (strcmp(key, "default_pad") == 0) {
if (strlen(default_pad_path) > 0) {
return strdup(default_pad_path);
}
}
return NULL;
}
int set_preference(const char* key, const char* value) {
if (strcmp(key, "default_pad") == 0) {
if (value) {
strncpy(default_pad_path, value, sizeof(default_pad_path) - 1);
default_pad_path[sizeof(default_pad_path) - 1] = '\0';
} else {
default_pad_path[0] = '\0';
}
return save_preferences();
}
return 1;
}
char* get_default_pad_path(void) {
if (strlen(default_pad_path) > 0) {
return strdup(default_pad_path);
}
return NULL;
}
int set_default_pad_path(const char* pad_path) {
if (!pad_path) {
return set_preference("default_pad", NULL);
}
// Ensure we store the full absolute path
char absolute_path[1024];
if (pad_path[0] == '/') {
// Already absolute path
strncpy(absolute_path, pad_path, sizeof(absolute_path) - 1);
absolute_path[sizeof(absolute_path) - 1] = '\0';
} else {
// Relative path - make it absolute
char current_dir[512];
if (getcwd(current_dir, sizeof(current_dir))) {
snprintf(absolute_path, sizeof(absolute_path), "%s/%s", current_dir, pad_path);
} else {
// Fallback to using the path as-is if getcwd fails
strncpy(absolute_path, pad_path, sizeof(absolute_path) - 1);
absolute_path[sizeof(absolute_path) - 1] = '\0';
}
}
return set_preference("default_pad", absolute_path);
}
// OTP thumb drive detection function implementation
int detect_otp_thumb_drive(char* otp_drive_path, size_t path_size) {
const char* mount_dirs[] = {"/media", "/run/media", "/mnt", NULL};
for (int mount_idx = 0; mount_dirs[mount_idx] != NULL; mount_idx++) {
DIR* mount_dir = opendir(mount_dirs[mount_idx]);
if (!mount_dir) continue;
struct dirent* mount_entry;
while ((mount_entry = readdir(mount_dir)) != NULL) {
if (mount_entry->d_name[0] == '.') continue;
char mount_path[1024]; // Increased buffer size
snprintf(mount_path, sizeof(mount_path), "%s/%s", mount_dirs[mount_idx], mount_entry->d_name);
// For /media, we need to go one level deeper (user directories)
if (strcmp(mount_dirs[mount_idx], "/media") == 0) {
// This is /media/[username] - look inside for drives
DIR* user_dir = opendir(mount_path);
if (!user_dir) continue;
struct dirent* user_entry;
while ((user_entry = readdir(user_dir)) != NULL) {
if (user_entry->d_name[0] == '.') continue;
// Check if drive name starts with "OTP"
if (strncmp(user_entry->d_name, "OTP", 3) != 0) continue;
char user_mount_path[2048]; // Increased buffer size
// Verify buffer has enough space before concatenation
size_t mount_len = strlen(mount_path);
size_t entry_len = strlen(user_entry->d_name);
if (mount_len + entry_len + 2 < sizeof(user_mount_path)) {
snprintf(user_mount_path, sizeof(user_mount_path), "%s/%s", mount_path, user_entry->d_name);
// Check if this is a readable directory
DIR* drive_dir = opendir(user_mount_path);
if (drive_dir) {
closedir(drive_dir);
strncpy(otp_drive_path, user_mount_path, path_size - 1);
otp_drive_path[path_size - 1] = '\0';
closedir(user_dir);
closedir(mount_dir);
return 1; // Found OTP drive
}
}
}
closedir(user_dir);
} else if (strcmp(mount_dirs[mount_idx], "/run/media") == 0) {
// For /run/media, we need to go one level deeper (skip username)
DIR* user_dir = opendir(mount_path);
if (!user_dir) continue;
struct dirent* user_entry;
while ((user_entry = readdir(user_dir)) != NULL) {
if (user_entry->d_name[0] == '.') continue;
// Check if drive name starts with "OTP"
if (strncmp(user_entry->d_name, "OTP", 3) != 0) continue;
char user_mount_path[2048]; // Increased buffer size
snprintf(user_mount_path, sizeof(user_mount_path), "%s/%s", mount_path, user_entry->d_name);
// Check if this is a readable directory
DIR* drive_dir = opendir(user_mount_path);
if (drive_dir) {
closedir(drive_dir);
strncpy(otp_drive_path, user_mount_path, path_size - 1);
otp_drive_path[path_size - 1] = '\0';
closedir(user_dir);
closedir(mount_dir);
return 1; // Found OTP drive
}
}
closedir(user_dir);
} else {
// Direct mount point (like /mnt/OTP_DRIVE)
// Check if drive name starts with "OTP"
if (strncmp(mount_entry->d_name, "OTP", 3) == 0) {
DIR* drive_dir = opendir(mount_path);
if (drive_dir) {
closedir(drive_dir);
strncpy(otp_drive_path, mount_path, path_size - 1);
otp_drive_path[path_size - 1] = '\0';
closedir(mount_dir);
return 1; // Found OTP drive
}
}
}
}
closedir(mount_dir);
}
return 0; // No OTP drive found
}
// Custom base64 encode function
char* custom_base64_encode(const unsigned char* input, int length) {
int output_length = 4 * ((length + 2) / 3);
char* encoded = malloc(output_length + 1);
if (!encoded) return NULL;
int i, j;
for (i = 0, j = 0; i < length;) {
uint32_t octet_a = i < length ? input[i++] : 0;
uint32_t octet_b = i < length ? input[i++] : 0;
uint32_t octet_c = i < length ? input[i++] : 0;
uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;
encoded[j++] = base64_chars[(triple >> 18) & 63];
encoded[j++] = base64_chars[(triple >> 12) & 63];
encoded[j++] = base64_chars[(triple >> 6) & 63];
encoded[j++] = base64_chars[triple & 63];
}
// Add padding
for (int pad = 0; pad < (3 - length % 3) % 3; pad++) {
encoded[output_length - 1 - pad] = '=';
}
encoded[output_length] = '\0';
return encoded;
}
// Custom base64 decode function
unsigned char* custom_base64_decode(const char* input, int* output_length) {
int input_length = strlen(input);
if (input_length % 4 != 0) return NULL;
*output_length = input_length / 4 * 3;
if (input[input_length - 1] == '=') (*output_length)--;
if (input[input_length - 2] == '=') (*output_length)--;
unsigned char* decoded = malloc(*output_length);
if (!decoded) return NULL;
int i, j;
for (i = 0, j = 0; i < input_length;) {
int sextet_a = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
int sextet_b = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
int sextet_c = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
int sextet_d = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
if (sextet_a == -1 || sextet_b == -1 || sextet_c == -1 || sextet_d == -1) {
free(decoded);
return NULL;
}
uint32_t triple = (sextet_a << 18) + (sextet_b << 12) + (sextet_c << 6) + sextet_d;
if (j < *output_length) decoded[j++] = (triple >> 16) & 255;
if (j < *output_length) decoded[j++] = (triple >> 8) & 255;
if (j < *output_length) decoded[j++] = triple & 255;
}
return decoded;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// ADD ENTROPY
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// TRUERNG DEVICE DETECTION AND COMMUNICATION
// Ported from true_rng/main.c for entropy collection
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Read USB device info from sysfs (ported from TrueRNG reference)
int read_usb_device_info(const char* port_name, char* vid, char* pid) {
char path[512];
FILE *fp;
// Try to read idVendor first (works for both ttyUSB and ttyACM devices)
snprintf(path, sizeof(path), "/sys/class/tty/%s/device/../idVendor", port_name);
fp = fopen(path, "r");
if (fp) {
if (fgets(vid, 8, fp) != NULL) {
// Remove newline if present
int len = strlen(vid);
if (len > 0 && vid[len-1] == '\n') {
vid[len-1] = '\0';
}
} else {
fclose(fp);
return 0;
}
fclose(fp);
} else {
return 0;
}
// Try to read idProduct
snprintf(path, sizeof(path), "/sys/class/tty/%s/device/../idProduct", port_name);
fp = fopen(path, "r");
if (fp) {
if (fgets(pid, 8, fp) != NULL) {
// Remove newline if present
int len = strlen(pid);
if (len > 0 && pid[len-1] == '\n') {
pid[len-1] = '\0';
}
} else {
fclose(fp);
return 0;
}
fclose(fp);
return 1;
} else {
return 0;
}
}
// Find TrueRNG device port (ported and adapted from TrueRNG reference)
// Returns: 0=not found, 1=TrueRNGproV2, 2=TrueRNGpro, 3=TrueRNG
int find_truerng_port(char* port_path, size_t port_path_size, truerng_device_type_t* device_type) {
DIR *dir;
struct dirent *entry;
char vid[8], pid[8];
int device_found = 0;
dir = opendir("/dev");
if (dir == NULL) {
return 0;
}
while ((entry = readdir(dir)) != NULL) {
// Look for ttyUSB* or ttyACM* devices
if (strncmp(entry->d_name, "ttyUSB", 6) == 0 ||
strncmp(entry->d_name, "ttyACM", 6) == 0) {
if (read_usb_device_info(entry->d_name, vid, pid)) {
// Convert to uppercase for comparison
for (int i = 0; vid[i]; i++) vid[i] = toupper(vid[i]);
for (int i = 0; pid[i]; i++) pid[i] = toupper(pid[i]);
// Check for TrueRNGproV2
if (strcmp(vid, TRUERNGPROV2_VID) == 0 && strcmp(pid, TRUERNGPROV2_PID) == 0) {
snprintf(port_path, port_path_size, "/dev/%s", entry->d_name);
*device_type = TRUERNG_PRO_V2;
device_found = 1;
break;
}
// Check for TrueRNGpro
if (strcmp(vid, TRUERNGPRO_VID) == 0 && strcmp(pid, TRUERNGPRO_PID) == 0) {
snprintf(port_path, port_path_size, "/dev/%s", entry->d_name);
*device_type = TRUERNG_PRO;
device_found = 2;
break;
}
// Check for TrueRNG
if (strcmp(vid, TRUERNG_VID) == 0 && strcmp(pid, TRUERNG_PID) == 0) {
snprintf(port_path, port_path_size, "/dev/%s", entry->d_name);
*device_type = TRUERNG_ORIGINAL;
device_found = 3;
break;
}
}
}
}
closedir(dir);
return device_found;
}
// Setup serial port for TrueRNG communication (ported from TrueRNG reference)
int setup_truerng_serial_port(const char* port_path) {
int fd;
struct termios tty;
fd = open(port_path, O_RDWR | O_NOCTTY);
if (fd < 0) {
return -1;
}
// Get current port settings
if (tcgetattr(fd, &tty) != 0) {
close(fd);
return -1;
}
// Set baud rate (TrueRNG devices use 9600)
cfsetospeed(&tty, B9600);
cfsetispeed(&tty, B9600);
// 8N1 mode
tty.c_cflag &= ~PARENB; // No parity
tty.c_cflag &= ~CSTOPB; // 1 stop bit
tty.c_cflag &= ~CSIZE; // Clear size bits
tty.c_cflag |= CS8; // 8 data bits
tty.c_cflag &= ~CRTSCTS; // No hardware flow control
tty.c_cflag |= CREAD | CLOCAL; // Enable reading and ignore modem controls
// Raw input mode
tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
tty.c_oflag &= ~OPOST;
// Set for blocking reads - wait for data indefinitely
tty.c_cc[VMIN] = 1; // Block until at least 1 character is received
tty.c_cc[VTIME] = 0; // No timeout
// Apply settings
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
close(fd);
return -1;
}
// Flush input buffer
tcflush(fd, TCIFLUSH);
// Set DTR
int status;
ioctl(fd, TIOCMGET, &status);
status |= TIOCM_DTR;
ioctl(fd, TIOCMSET, &status);
return fd;
}
// Get friendly name for TrueRNG device type
const char* get_truerng_device_name(truerng_device_type_t device_type) {
switch (device_type) {
case TRUERNG_PRO_V2: return "TrueRNGproV2";
case TRUERNG_PRO: return "TrueRNGpro";
case TRUERNG_ORIGINAL: return "TrueRNG";
default: return "Unknown";
}
}
// Collect entropy from TrueRNG device with equivalent quality to keyboard entropy
int collect_truerng_entropy(unsigned char* entropy_buffer, size_t target_bytes,
size_t* collected_bytes, int display_progress) {
char port_path[512];
truerng_device_type_t device_type;
int serial_fd = -1;
// Find TrueRNG device
if (!find_truerng_port(port_path, sizeof(port_path), &device_type)) {
if (display_progress) {
printf("No TrueRNG device found.\n");
printf("\nSupported devices:\n");
printf(" - TrueRNG (PID: %s, VID: %s)\n", TRUERNG_VID, TRUERNG_PID);
printf(" - TrueRNGpro (PID: %s, VID: %s)\n", TRUERNGPRO_VID, TRUERNGPRO_PID);
printf(" - TrueRNGproV2 (PID: %s, VID: %s)\n", TRUERNGPROV2_VID, TRUERNGPROV2_PID);
printf("\nPlease connect a TrueRNG device and try again.\n");
}
return 1; // Device not found
}
if (display_progress) {
printf("Found %s at %s\n", get_truerng_device_name(device_type), port_path);
printf("Collecting %zu bytes of entropy...\n", target_bytes);
}
// Setup serial port
serial_fd = setup_truerng_serial_port(port_path);
if (serial_fd < 0) {
if (display_progress) {
printf("Error: Cannot open TrueRNG device at %s\n", port_path);
printf("Check device permissions or run as root.\n");
}
return 2; // Serial port setup failed
}
// Collect entropy data
size_t bytes_read = 0;
unsigned char buffer[1024]; // Read in 1KB chunks
time_t start_time = time(NULL);
while (bytes_read < target_bytes) {
size_t chunk_size = sizeof(buffer);
if (target_bytes - bytes_read < chunk_size) {
chunk_size = target_bytes - bytes_read;
}
size_t bytes_in_chunk = 0;
while (bytes_in_chunk < chunk_size) {
ssize_t result = read(serial_fd, buffer + bytes_in_chunk, chunk_size - bytes_in_chunk);
if (result < 0) {
if (display_progress) {
printf("Error: Failed to read from TrueRNG device\n");
}
close(serial_fd);
return 3; // Read failed
} else if (result == 0) {
if (display_progress) {
printf("Error: No data received from TrueRNG device\n");
}
close(serial_fd);
return 4; // No data
}
bytes_in_chunk += result;
}
// Copy to entropy buffer
memcpy(entropy_buffer + bytes_read, buffer, chunk_size);
bytes_read += chunk_size;
// Show progress for large collections
if (display_progress && bytes_read % (4 * 1024) == 0) { // Every 4KB
double percentage = (double)bytes_read / target_bytes * 100.0;
printf("Progress: %.1f%% (%zu/%zu bytes)\r", percentage, bytes_read, target_bytes);
fflush(stdout);
}
}
close(serial_fd);
if (display_progress) {
double collection_time = difftime(time(NULL), start_time);
printf("\n✓ TrueRNG entropy collection complete!\n");
printf(" Collected: %zu bytes in %.1f seconds\n", bytes_read, collection_time);
printf(" Device: %s\n", get_truerng_device_name(device_type));
if (collection_time > 0) {
printf(" Rate: %.1f KB/s\n", (double)bytes_read / collection_time / 1024.0);
}
}
*collected_bytes = bytes_read;
return 0; // Success
}
// Helper function to format time in human-readable format
void format_time_remaining(double seconds, char* buffer, size_t buffer_size) {
if (seconds < 60) {
snprintf(buffer, buffer_size, "%.0fs", seconds);
} else if (seconds < 3600) {
int mins = (int)(seconds / 60);
int secs = (int)(seconds) % 60;
snprintf(buffer, buffer_size, "%dm %02ds", mins, secs);
} else {
int hours = (int)(seconds / 3600);
int mins = (int)(seconds / 60) % 60;
snprintf(buffer, buffer_size, "%dh %02dm", hours, mins);
}
}
// Streaming TrueRNG entropy collection for full pad enhancement
// This function applies entropy directly to the pad in chunks to avoid memory issues
int collect_truerng_entropy_streaming(const char* pad_chksum, size_t total_bytes, int display_progress) {
char port_path[512];
truerng_device_type_t device_type;
int serial_fd = -1;
// Find TrueRNG device
if (!find_truerng_port(port_path, sizeof(port_path), &device_type)) {
if (display_progress) {
printf("No TrueRNG device found.\n");
printf("\nSupported devices:\n");
printf(" - TrueRNG (PID: %s, VID: %s)\n", TRUERNG_VID, TRUERNG_PID);
printf(" - TrueRNGpro (PID: %s, VID: %s)\n", TRUERNGPRO_VID, TRUERNGPRO_PID);
printf(" - TrueRNGproV2 (PID: %s, VID: %s)\n", TRUERNGPROV2_VID, TRUERNGPROV2_PID);
printf("\nPlease connect a TrueRNG device and try again.\n");
}
return 1; // Device not found
}
if (display_progress) {
printf("Found %s at %s\n", get_truerng_device_name(device_type), port_path);
printf("Streaming %zu bytes of entropy directly to pad...\n", total_bytes);
}
// Setup serial port
serial_fd = setup_truerng_serial_port(port_path);
if (serial_fd < 0) {
if (display_progress) {
printf("Error: Cannot open TrueRNG device at %s\n", port_path);
printf("Check device permissions or run as root.\n");
}
return 2; // Serial port setup failed
}
// For large pads (>10MB), do a 1MB test to estimate completion time
double estimated_rate = 0.0;
int user_confirmed = 1; // Default to confirmed for small pads
if (total_bytes > 10 * 1024 * 1024 && display_progress) {
printf("\nLarge pad detected (%.1f MB). Running 1MB speed test...\n",
(double)total_bytes / (1024.0 * 1024.0));
// Test with 1MB sample
size_t test_bytes = 1024 * 1024; // 1MB
unsigned char* test_buffer = malloc(test_bytes);
if (!test_buffer) {
printf("Error: Cannot allocate test buffer\n");
close(serial_fd);
return 3;
}
time_t test_start = time(NULL);
size_t test_collected = 0;
while (test_collected < test_bytes) {
size_t bytes_needed = test_bytes - test_collected;
size_t read_size = (bytes_needed > 1024) ? 1024 : bytes_needed;
ssize_t result = read(serial_fd, test_buffer + test_collected, read_size);
if (result <= 0) {
printf("Error: TrueRNG test failed\n");
free(test_buffer);
close(serial_fd);
return 4;
}
test_collected += result;
// Show test progress
double test_progress = (double)test_collected / test_bytes * 100.0;
printf("\rSpeed test: %.1f%% complete", test_progress);
fflush(stdout);
}
double test_time = difftime(time(NULL), test_start);
estimated_rate = (double)test_bytes / test_time; // bytes per second
// Calculate estimated total time
double estimated_total_time = (double)total_bytes / estimated_rate;
char time_str[64];
format_time_remaining(estimated_total_time, time_str, sizeof(time_str));
printf("\nSpeed test complete: %.1f KB/s\n", estimated_rate / 1024.0);
printf("Estimated completion time: %s\n", time_str);
printf("\nProceed with full pad enhancement? (y/N): ");
fflush(stdout);
char response[10];
if (fgets(response, sizeof(response), stdin) == NULL ||
(response[0] != 'y' && response[0] != 'Y')) {
printf("Operation cancelled by user.\n");
free(test_buffer);
close(serial_fd);
return 5; // User cancelled
}
user_confirmed = 1;
free(test_buffer);
// Reset serial port for main operation
close(serial_fd);
serial_fd = setup_truerng_serial_port(port_path);
if (serial_fd < 0) {
printf("Error: Cannot reopen TrueRNG device\n");
return 6;
}
}
if (!user_confirmed) {
close(serial_fd);
return 5; // User cancelled
}
// Get pad paths
char pad_path[1024];
char state_path[1024];
get_pad_path(pad_chksum, pad_path, state_path);
// Open pad file for read/write with temporary permissions
if (chmod(pad_path, S_IRUSR | S_IWUSR) != 0) {
if (display_progress) {
printf("Error: Cannot change pad file permissions\n");
}
close(serial_fd);
return 3;
}
FILE* pad_file = fopen(pad_path, "r+b");
if (!pad_file) {
if (display_progress) {
printf("Error: Cannot open pad file for modification: %s\n", pad_path);
}
chmod(pad_path, S_IRUSR); // Restore read-only
close(serial_fd);
return 4;
}
// Process pad in chunks
const size_t chunk_size = 1024 * 1024; // 1MB chunks
unsigned char* entropy_chunk = malloc(chunk_size);
unsigned char* pad_chunk = malloc(chunk_size);
unsigned char* mixed_chunk = malloc(chunk_size);
if (!entropy_chunk || !pad_chunk || !mixed_chunk) {
if (display_progress) {
printf("Error: Cannot allocate chunk buffers\n");
}
free(entropy_chunk);
free(pad_chunk);
free(mixed_chunk);
fclose(pad_file);
chmod(pad_path, S_IRUSR);
close(serial_fd);
return 5;
}
size_t bytes_processed = 0;
time_t start_time = time(NULL);
while (bytes_processed < total_bytes) {
size_t current_chunk_size = chunk_size;
if (total_bytes - bytes_processed < chunk_size) {
current_chunk_size = total_bytes - bytes_processed;
}
// Collect entropy from TrueRNG for this chunk
size_t entropy_bytes_in_chunk = 0;
while (entropy_bytes_in_chunk < current_chunk_size) {
size_t bytes_needed = current_chunk_size - entropy_bytes_in_chunk;
size_t read_size = (bytes_needed > 1024) ? 1024 : bytes_needed;
ssize_t result = read(serial_fd, entropy_chunk + entropy_bytes_in_chunk, read_size);
if (result < 0) {
if (display_progress) {
printf("Error: Failed to read from TrueRNG device\n");
}
free(entropy_chunk);
free(pad_chunk);
free(mixed_chunk);
fclose(pad_file);
chmod(pad_path, S_IRUSR);
close(serial_fd);
return 6;
} else if (result == 0) {
if (display_progress) {
printf("Error: No data received from TrueRNG device\n");
}
free(entropy_chunk);
free(pad_chunk);
free(mixed_chunk);
fclose(pad_file);
chmod(pad_path, S_IRUSR);
close(serial_fd);
return 7;
}
entropy_bytes_in_chunk += result;
}
// Read current pad data for this chunk
if (fseek(pad_file, bytes_processed, SEEK_SET) != 0) {
if (display_progress) {
printf("Error: Cannot seek to offset %zu in pad file\n", bytes_processed);
}
free(entropy_chunk);
free(pad_chunk);
free(mixed_chunk);
fclose(pad_file);
chmod(pad_path, S_IRUSR);
close(serial_fd);
return 8;
}
if (fread(pad_chunk, 1, current_chunk_size, pad_file) != current_chunk_size) {
if (display_progress) {
printf("Error: Cannot read pad data at offset %zu\n", bytes_processed);
}
free(entropy_chunk);
free(pad_chunk);
free(mixed_chunk);
fclose(pad_file);
chmod(pad_path, S_IRUSR);
close(serial_fd);
return 9;
}
// Derive Chacha20 key and nonce from entropy chunk
unsigned char key[32], nonce[12];
if (derive_chacha20_params(entropy_chunk, current_chunk_size, key, nonce) != 0) {
if (display_progress) {
printf("Error: Failed to derive Chacha20 parameters from entropy\n");
}
free(entropy_chunk);
free(pad_chunk);
free(mixed_chunk);
fclose(pad_file);
chmod(pad_path, S_IRUSR);
close(serial_fd);
return 10;
}
// Generate keystream and XOR with pad data
uint32_t counter = bytes_processed / 64; // Chacha20 block counter
if (chacha20_encrypt(key, counter, nonce, pad_chunk, mixed_chunk, current_chunk_size) != 0) {
if (display_progress) {
printf("Error: Chacha20 keystream generation failed\n");
}
free(entropy_chunk);
free(pad_chunk);
free(mixed_chunk);
fclose(pad_file);
chmod(pad_path, S_IRUSR);
close(serial_fd);
return 11;
}
// XOR existing pad with keystream (adds entropy)
for (size_t i = 0; i < current_chunk_size; i++) {
mixed_chunk[i] = pad_chunk[i] ^ mixed_chunk[i];
}
// Write modified data back to pad
if (fseek(pad_file, bytes_processed, SEEK_SET) != 0) {
if (display_progress) {
printf("Error: Cannot seek to write offset %zu\n", bytes_processed);
}
free(entropy_chunk);
free(pad_chunk);
free(mixed_chunk);
fclose(pad_file);
chmod(pad_path, S_IRUSR);
close(serial_fd);
return 12;
}
if (fwrite(mixed_chunk, 1, current_chunk_size, pad_file) != current_chunk_size) {
if (display_progress) {
printf("Error: Cannot write modified pad data\n");
}
free(entropy_chunk);
free(pad_chunk);
free(mixed_chunk);
fclose(pad_file);
chmod(pad_path, S_IRUSR);
close(serial_fd);
return 13;
}
bytes_processed += current_chunk_size;
// Show progress and entropy samples for visual verification
if (display_progress) {
// Update progress more frequently for better user experience - always show cumulative amount
if (bytes_processed % (4 * 1024 * 1024) == 0 || bytes_processed == total_bytes) { // Every 4MB or at completion
double percentage = (double)bytes_processed / total_bytes * 100.0;
double elapsed = difftime(time(NULL), start_time);
double rate = 0.0;
if (elapsed > 0) {
rate = (double)bytes_processed / elapsed / (1024.0 * 1024.0);
}
// Calculate estimated time remaining
char eta_str[64] = "";
if (rate > 0.0 && bytes_processed < total_bytes) {
double remaining_bytes = (double)(total_bytes - bytes_processed);
double eta_seconds = remaining_bytes / (rate * 1024.0 * 1024.0); // Convert rate back to bytes/sec
format_time_remaining(eta_seconds, eta_str, sizeof(eta_str));
}
// Clear previous line and show progress bar with cumulative TrueRNG data generated
printf("\r\033[K"); // Clear line
printf("[");
int bar_width = 30;
int filled = (int)(percentage / 100.0 * bar_width);
for (int i = 0; i < filled; i++) printf("");
for (int i = filled; i < bar_width; i++) printf("");
if (strlen(eta_str) > 0) {
printf("] %.1f%% - TrueRNG: %zu MB / %zu MB (%.1f MB/s) ETA: %s",
percentage, bytes_processed / (1024*1024), total_bytes / (1024*1024), rate, eta_str);
} else {
printf("] %.1f%% - TrueRNG: %zu MB / %zu MB (%.1f MB/s)",
percentage, bytes_processed / (1024*1024), total_bytes / (1024*1024), rate);
}
fflush(stdout);
}
// Show entropy samples every 64MB for visual verification of randomness
if (bytes_processed % (64 * 1024 * 1024) == 0 && bytes_processed > 0) {
printf("\n🔬 TrueRNG entropy sample: ");
// Display first 16 bytes of current entropy chunk as hex
size_t sample_size = (current_chunk_size < 16) ? current_chunk_size : 16;
for (size_t i = 0; i < sample_size; i++) {
printf("%02x", entropy_chunk[i]);
if (i == 7) printf(" "); // Space in middle for readability
}
printf("\n");
}
}
}
// Cleanup
free(entropy_chunk);
free(pad_chunk);
free(mixed_chunk);
fclose(pad_file);
close(serial_fd);
// Restore read-only permissions
if (chmod(pad_path, S_IRUSR) != 0) {
if (display_progress) {
printf("Warning: Cannot restore pad file to read-only\n");
}
}
if (display_progress) {
double collection_time = difftime(time(NULL), start_time);
printf("\n✓ TrueRNG streaming entropy collection complete!\n");
printf(" Enhanced: %zu bytes in %.1f seconds\n", bytes_processed, collection_time);
printf(" Device: %s\n", get_truerng_device_name(device_type));
if (collection_time > 0) {
printf(" Rate: %.1f MB/s\n", (double)bytes_processed / collection_time / (1024.0*1024.0));
}
printf("✓ Pad integrity maintained\n");
printf("✓ Entropy distributed across entire pad\n");
printf("✓ Pad restored to read-only mode\n");
// Pause before returning to menu to let user see the success message
print_centered_header("TrueRNG Enhancement Complete", 1);
}
return 0; // Success
}
// Collect dice entropy with manual input validation
int collect_dice_entropy(unsigned char* entropy_buffer, size_t target_bytes,
size_t* collected_bytes, int display_progress) {
if (display_progress) {
print_centered_header("Dice Entropy Collection", 0);
printf("Enter dice rolls as sequences of digits 1-6.\n");
printf("Target: %zu bytes (%zu dice rolls needed)\n", target_bytes, target_bytes * 4);
printf("Press Enter after each sequence, or 'done' when finished.\n\n");
}
size_t entropy_bits = 0;
size_t target_bits = target_bytes * 8;
unsigned char current_byte = 0;
int bits_in_byte = 0;
size_t bytes_written = 0;
char input[256];
while (entropy_bits < target_bits && bytes_written < target_bytes) {
if (display_progress) {
double percentage = (double)entropy_bits / target_bits * 100.0;
printf("Progress: %.1f%% (%zu/%zu bits) - Enter dice rolls: ",
percentage, entropy_bits, target_bits);
fflush(stdout);
}
if (!fgets(input, sizeof(input), stdin)) {
if (display_progress) {
printf("Error: Failed to read input\n");
}
return 1;
}
// Remove newline
input[strcspn(input, "\n")] = 0;
// Check for done command
if (strcmp(input, "done") == 0 && entropy_bits >= target_bits / 2) {
break; // Allow early exit if we have at least half the target
}
// Process dice rolls
for (size_t i = 0; input[i] && entropy_bits < target_bits && bytes_written < target_bytes; i++) {
char c = input[i];
if (c >= '1' && c <= '6') {
// Convert dice roll (1-6) to 3 bits of entropy
unsigned char roll_value = c - '1'; // 0-5
// Pack 3 bits into current byte
for (int bit = 2; bit >= 0 && entropy_bits < target_bits; bit--) {
current_byte = (current_byte << 1) | ((roll_value >> bit) & 1);
bits_in_byte++;
entropy_bits++;
if (bits_in_byte == 8) {
entropy_buffer[bytes_written++] = current_byte;
current_byte = 0;
bits_in_byte = 0;
}
}
}
}
}
// Handle partial byte
if (bits_in_byte > 0 && bytes_written < target_bytes) {
// Pad remaining bits with zeros
current_byte <<= (8 - bits_in_byte);
entropy_buffer[bytes_written++] = current_byte;
}
if (display_progress) {
printf("\n✓ Dice entropy collection complete!\n");
printf(" Collected: %zu bytes from dice rolls\n", bytes_written);
printf(" Entropy bits: %zu\n", entropy_bits);
}
*collected_bytes = bytes_written;
return 0; // Success
}
// Collect entropy by source type with unified interface
int collect_entropy_by_source(entropy_source_t source, unsigned char* entropy_buffer,
size_t target_bytes, size_t* collected_bytes, int display_progress) {
switch (source) {
case ENTROPY_SOURCE_KEYBOARD:
return collect_entropy_with_feedback(entropy_buffer, target_bytes, collected_bytes, 1);
case ENTROPY_SOURCE_TRUERNG:
return collect_truerng_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress);
case ENTROPY_SOURCE_DICE:
return collect_dice_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress);
default:
if (display_progress) {
printf("Error: Unknown entropy source\n");
}
return 1;
}
}
double get_precise_time(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec / 1000000000.0;
}
void draw_progress_bar(double percentage, int width) {
int filled = (int)(percentage / 100.0 * width);
if (filled > width) filled = width;
printf("[");
for (int i = 0; i < filled; i++) {
printf("");
}
for (int i = filled; i < width; i++) {
printf("");
}
printf("]");
}
void draw_quality_bar(double quality, int width, const char* label) {
int filled = (int)(quality / 100.0 * width);
if (filled > width) filled = width;
// Color coding based on quality
const char* color;
if (quality >= 80) color = "\033[32m"; // Green
else if (quality >= 60) color = "\033[33m"; // Yellow
else color = "\033[31m"; // Red
printf("%s", color);
draw_progress_bar(quality, width);
printf("\033[0m %-10s", label); // Reset color
}
double calculate_timing_quality(const entropy_collection_state_t* state) {
// Analyze timing variance between keypresses
if (state->collected_bytes < 32) return 0.0; // Need minimum data
// Simplified timing quality based on collection rate and variation
double elapsed = get_precise_time() - state->collection_start_time;
if (elapsed < 0.1) return 0.0;
double rate = state->collected_bytes / elapsed;
// Optimal rate is around 50-200 bytes/second (moderate typing with good timing variance)
if (rate >= 50 && rate <= 200) return 90.0;
if (rate >= 20 && rate <= 500) return 70.0;
if (rate >= 10 && rate <= 1000) return 50.0;
return 30.0;
}
double calculate_variety_quality(const entropy_collection_state_t* state) {
// Analyze key variety and distribution
if (state->collected_bytes < 16) return 0.0;
// Calculate entropy from key histogram
double entropy = 0.0;
size_t total_keys = 0;
// Count total keypresses
for (int i = 0; i < 256; i++) {
total_keys += state->key_histogram[i];
}
if (total_keys == 0) return 0.0;
// Calculate Shannon entropy
for (int i = 0; i < 256; i++) {
if (state->key_histogram[i] > 0) {
double p = (double)state->key_histogram[i] / total_keys;
entropy -= p * log2(p);
}
}
// Convert entropy to quality score (0-100)
double max_entropy = log2(256); // Perfect entropy for 8-bit keyspace
double normalized_entropy = entropy / max_entropy;
// Scale based on unique keys as well
double unique_key_factor = (double)state->unique_keys / 50.0; // 50+ unique keys is excellent
if (unique_key_factor > 1.0) unique_key_factor = 1.0;
return (normalized_entropy * 70.0 + unique_key_factor * 30.0);
}
unsigned char calculate_overall_quality(const entropy_collection_state_t* state) {
double timing = calculate_timing_quality(state);
double variety = calculate_variety_quality(state);
// Simple collection progress bonus
double progress_bonus = (double)state->collected_bytes / state->target_bytes * 20.0;
if (progress_bonus > 20.0) progress_bonus = 20.0;
// Weighted average
double overall = (timing * 0.4 + variety * 0.4 + progress_bonus);
if (overall > 100.0) overall = 100.0;
return (unsigned char)overall;
}
void display_entropy_progress(const entropy_collection_state_t* state) {
// Calculate percentages
double progress = (double)state->collected_bytes / state->target_bytes * 100.0;
if (progress > 100.0) progress = 100.0;
double quality = state->quality_score;
double timing_quality = calculate_timing_quality(state);
double variety_quality = calculate_variety_quality(state);
// Clear previous output and redraw
printf("\033[2K\r"); // Clear line
printf("\033[A\033[2K\r"); // Move up and clear
printf("\033[A\033[2K\r"); // Move up and clear
printf("\033[A\033[2K\r"); // Move up and clear
printf("\033[A\033[2K\r"); // Move up and clear
printf("\033[A\033[2K\r"); // Move up and clear
// Header
printf("Adding Entropy to Pad - Target: %zu bytes\n\n", state->target_bytes);
// Main progress bar
printf("Progress: ");
draw_progress_bar(progress, 50);
printf(" %.1f%% (%zu/%zu bytes)\n", progress, state->collected_bytes, state->target_bytes);
// Quality indicators
printf("Quality: ");
draw_quality_bar(quality, 50, "OVERALL");
printf("\n");
printf("Timing: ");
draw_quality_bar(timing_quality, 50, "VARIED");
printf("\n");
printf("Keys: ");
draw_quality_bar(variety_quality, 50, "DIVERSE");
printf("\n");
// Instructions
if (state->collected_bytes >= 1024 && state->auto_complete_enabled) {
printf("\nPress ESC to finish (minimum reached) or continue typing...");
} else if (state->collected_bytes < 1024) {
printf("\nType random keys... (%zu more bytes needed)", 1024 - state->collected_bytes);
} else {
printf("\nType random keys or press ESC when satisfied...");
}
fflush(stdout);
}
// Chacha20 key derivation from collected entropy
int derive_chacha20_params(const unsigned char* entropy_data, size_t entropy_size,
unsigned char key[32], unsigned char nonce[12]) {
if (!entropy_data || entropy_size < 512 || !key || !nonce) {
return 1; // Error: insufficient entropy or null pointers
}
// Phase 1: Generate base key from entropy using enhanced XOR checksum method
unsigned char enhanced_checksum[44]; // 32 key + 12 nonce
memset(enhanced_checksum, 0, 44);
// Mix entropy data similar to calculate_checksum but for 44 bytes
for (size_t i = 0; i < entropy_size; i++) {
unsigned char bucket = i % 44;
enhanced_checksum[bucket] ^= entropy_data[i] ^
((i >> 8) & 0xFF) ^
((i >> 16) & 0xFF) ^
((i >> 24) & 0xFF);
}
// Phase 2: Add system entropy for additional randomness
unsigned char system_entropy[32];
FILE* urandom = fopen("/dev/urandom", "rb");
if (!urandom) {
return 2; // Error: cannot access system entropy
}
if (fread(system_entropy, 1, 32, urandom) != 32) {
fclose(urandom);
return 2; // Error: insufficient system entropy
}
fclose(urandom);
// Mix system entropy into derived key
for (int i = 0; i < 32; i++) {
enhanced_checksum[i] ^= system_entropy[i];
}
// Extract key and nonce
memcpy(key, enhanced_checksum, 32);
memcpy(nonce, enhanced_checksum + 32, 12);
return 0; // Success
}
// Check if a pad is unused (0% usage)
int is_pad_unused(const char* pad_chksum) {
uint64_t used_bytes;
if (read_state_offset(pad_chksum, &used_bytes) != 0) {
return 0; // Error reading state, assume used
}
return (used_bytes <= 32); // Only reserved bytes used (32 bytes for checksum encryption)
}
// Safely rename pad files (pad and state) from old to new checksum
int rename_pad_files_safely(const char* old_chksum, const char* new_chksum) {
char old_pad_path[1024], new_pad_path[1024];
char old_state_path[1024], new_state_path[1024];
// Construct file paths
snprintf(old_pad_path, sizeof(old_pad_path), "%s/%s.pad", current_pads_dir, old_chksum);
snprintf(new_pad_path, sizeof(new_pad_path), "%s/%s.pad", current_pads_dir, new_chksum);
snprintf(old_state_path, sizeof(old_state_path), "%s/%s.state", current_pads_dir, old_chksum);
snprintf(new_state_path, sizeof(new_state_path), "%s/%s.state", current_pads_dir, new_chksum);
// Check if new files would conflict with existing files
if (access(new_pad_path, F_OK) == 0) {
printf("Error: New pad file already exists: %s\n", new_pad_path);
return 1; // Conflict
}
// Rename pad file
if (rename(old_pad_path, new_pad_path) != 0) {
printf("Error: Failed to rename pad file from %s to %s\n", old_pad_path, new_pad_path);
return 2; // Pad rename failed
}
// Rename state file (if it exists)
if (access(old_state_path, F_OK) == 0) {
if (rename(old_state_path, new_state_path) != 0) {
printf("Warning: Failed to rename state file, but pad file was renamed successfully\n");
// Try to rollback pad file rename
rename(new_pad_path, old_pad_path);
return 3; // State rename failed
}
}
return 0; // Success
}
// Update pad checksum after entropy addition
int update_pad_checksum_after_entropy(const char* old_chksum, char* new_chksum) {
char pad_path[1024];
snprintf(pad_path, sizeof(pad_path), "%s/%s.pad", current_pads_dir, old_chksum);
// Calculate new checksum of the modified pad
if (calculate_checksum(pad_path, new_chksum) != 0) {
printf("Error: Cannot calculate new pad checksum\n");
return 1;
}
// Check if checksum actually changed
if (strcmp(old_chksum, new_chksum) == 0) {
printf("Warning: Pad checksum unchanged after entropy addition\n");
return 2; // Checksum didn't change (unusual but not fatal)
}
// Rename pad files to use new checksum
if (rename_pad_files_safely(old_chksum, new_chksum) != 0) {
return 3; // Rename failed
}
// Update default pad preference if this was the default pad
char* current_default = get_default_pad_path();
if (current_default) {
// Check if the old pad was the default
if (strstr(current_default, old_chksum)) {
// Update to new checksum
char new_default_path[1024];
snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, new_chksum);
if (set_default_pad_path(new_default_path) != 0) {
printf("Warning: Failed to update default pad preference\n");
} else {
printf("Updated default pad to new checksum: %.16s...\n", new_chksum);
}
}
free(current_default);
}
return 0; // Success
}
// Enhanced entropy collection with visual feedback
int collect_entropy_with_feedback(unsigned char* entropy_buffer, size_t target_bytes,
size_t* collected_bytes, int allow_early_exit) {
struct termios original_termios;
entropy_collection_state_t state = {0};
// Initialize state
state.target_bytes = target_bytes;
state.auto_complete_enabled = allow_early_exit;
state.collection_start_time = get_precise_time();
// Setup raw terminal
if (setup_raw_terminal(&original_termios) != 0) {
printf("Error: Cannot setup terminal for entropy collection\n");
return 1;
}
// Clear screen area for display
printf("\n\n\n\n\n\n");
unsigned char entropy_block[16];
struct timespec timestamp;
uint32_t sequence_counter = 0;
char key;
unsigned char seen_keys[256] = {0};
*collected_bytes = 0;
while (state.collected_bytes < target_bytes) {
// Update display
state.quality_score = calculate_overall_quality(&state);
display_entropy_progress(&state);
// Non-blocking read
if (read(STDIN_FILENO, &key, 1) == 1) {
// Handle ESC key for early exit
if (key == 27 && allow_early_exit && state.collected_bytes >= 1024) {
break; // Early exit allowed
}
// Record keypress timing
double current_time = get_precise_time();
state.last_keypress_time = current_time;
// Update key histogram
state.key_histogram[(unsigned char)key]++;
// Get high precision timestamp
clock_gettime(CLOCK_MONOTONIC, &timestamp);
// Create enhanced entropy block: [key][timestamp][sequence][quality_bits]
entropy_block[0] = key;
memcpy(&entropy_block[1], &timestamp.tv_sec, 8);
memcpy(&entropy_block[9], &timestamp.tv_nsec, 4);
memcpy(&entropy_block[13], &sequence_counter, 2);
entropy_block[15] = (unsigned char)(current_time * 1000) & 0xFF; // Sub-millisecond timing
// Add to entropy buffer
if (state.collected_bytes + 16 <= MAX_ENTROPY_BUFFER) {
memcpy(entropy_buffer + state.collected_bytes, entropy_block, 16);
state.collected_bytes += 16;
}
sequence_counter++;
// Track unique keys
if (!seen_keys[(unsigned char)key]) {
seen_keys[(unsigned char)key] = 1;
state.unique_keys++;
}
} else {
// No key available, just sleep and wait for keystrokes
usleep(10000); // 10ms delay - wait for keystrokes, don't add timing entropy
}
// Auto-complete at target if enabled
if (state.collected_bytes >= target_bytes) {
break;
}
}
// Final display update
state.quality_score = calculate_overall_quality(&state);
display_entropy_progress(&state);
// Summary
double collection_time = get_precise_time() - state.collection_start_time;
printf("\n\n✓ Entropy collection complete!\n");
printf(" Collected: %zu bytes in %.1f seconds\n", state.collected_bytes, collection_time);
printf(" Quality: %d%% (Excellent: 80%%+, Good: 60%%+)\n", state.quality_score);
printf(" Unique keys: %zu\n", state.unique_keys);
// Restore terminal
restore_terminal(&original_termios);
*collected_bytes = state.collected_bytes;
return 0;
}
// Keyboard entropy functions
int setup_raw_terminal(struct termios* original_termios) {
struct termios new_termios;
if (tcgetattr(STDIN_FILENO, original_termios) != 0) {
return 1;
}
new_termios = *original_termios;
new_termios.c_lflag &= ~(ICANON | ECHO);
new_termios.c_cc[VMIN] = 0;
new_termios.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSANOW, &new_termios) != 0) {
return 1;
}
// Set stdin to non-blocking
int flags = fcntl(STDIN_FILENO, F_GETFL);
if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) == -1) {
tcsetattr(STDIN_FILENO, TCSANOW, original_termios);
return 1;
}
return 0;
}
void restore_terminal(struct termios* original_termios) {
tcsetattr(STDIN_FILENO, TCSANOW, original_termios);
// Reset stdin to blocking
int flags = fcntl(STDIN_FILENO, F_GETFL);
fcntl(STDIN_FILENO, F_SETFL, flags & ~O_NONBLOCK);
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// UNIVERSAL CORE FUNCTIONS FOR CODE CONSOLIDATION
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Universal XOR operation - handles both encryption and decryption
// Since XOR is symmetric, this single function replaces all 6 duplicate XOR loops
int universal_xor_operation(const unsigned char* data, size_t data_len,
const unsigned char* pad_data, unsigned char* result) {
if (!data || !pad_data || !result) {
return 1; // Error: null pointer
}
for (size_t i = 0; i < data_len; i++) {
result[i] = data[i] ^ pad_data[i];
}
return 0; // Success
}
// Universal ASCII message parser - consolidates 4 duplicate parsing implementations
// Extracts checksum, offset, and base64 data from ASCII armored messages
int parse_ascii_message(const char* message, char* chksum, uint64_t* offset, char* base64_data) {
if (!message || !chksum || !offset || !base64_data) {
return 1; // Error: null pointer
}
char *message_copy = strdup(message);
if (!message_copy) {
return 1; // Memory allocation failed
}
char *line_ptr = strtok(message_copy, "\n");
int found_begin = 0;
int in_data_section = 0;
int found_chksum = 0, found_offset = 0;
// Initialize output
chksum[0] = '\0';
*offset = 0;
base64_data[0] = '\0';
while (line_ptr != NULL) {
if (strcmp(line_ptr, "-----BEGIN OTP MESSAGE-----") == 0) {
found_begin = 1;
}
else if (strcmp(line_ptr, "-----END OTP MESSAGE-----") == 0) {
break;
}
else if (found_begin) {
if (strncmp(line_ptr, "Pad-ChkSum: ", 12) == 0) {
strncpy(chksum, line_ptr + 12, 64);
chksum[64] = '\0';
found_chksum = 1;
}
else if (strncmp(line_ptr, "Pad-Offset: ", 12) == 0) {
*offset = strtoull(line_ptr + 12, NULL, 10);
found_offset = 1;
}
else if (strlen(line_ptr) == 0) {
in_data_section = 1;
}
else if (in_data_section) {
strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1);
}
else if (strncmp(line_ptr, "Version:", 8) != 0 && strncmp(line_ptr, "Pad-", 4) != 0) {
// This might be base64 data without a blank line separator
strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1);
}
}
line_ptr = strtok(NULL, "\n");
}
free(message_copy);
if (!found_begin || !found_chksum || !found_offset) {
return 2; // Error: incomplete message format
}
return 0; // Success
}
// Universal pad data loader - consolidates pad loading and validation logic
// Loads pad data at specified offset and validates pad availability
int load_pad_data(const char* pad_chksum, uint64_t offset, size_t length, unsigned char** pad_data) {
if (!pad_chksum || !pad_data) {
return 1; // Error: null pointer
}
char pad_path[1024];
char state_path[1024];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if pad file exists
if (access(pad_path, R_OK) != 0) {
return 2; // Error: pad file not found
}
// Check pad file size
struct stat pad_stat;
if (stat(pad_path, &pad_stat) != 0) {
return 3; // Error: cannot get pad file size
}
if (offset + length > (uint64_t)pad_stat.st_size) {
return 4; // Error: not enough pad space
}
// Allocate memory for pad data
*pad_data = malloc(length);
if (!*pad_data) {
return 5; // Error: memory allocation failed
}
// Open and read pad file
FILE* pad_file = fopen(pad_path, "rb");
if (!pad_file) {
free(*pad_data);
*pad_data = NULL;
return 6; // Error: cannot open pad file
}
if (fseek(pad_file, offset, SEEK_SET) != 0) {
fclose(pad_file);
free(*pad_data);
*pad_data = NULL;
return 7; // Error: cannot seek to offset
}
if (fread(*pad_data, 1, length, pad_file) != length) {
fclose(pad_file);
free(*pad_data);
*pad_data = NULL;
return 8; // Error: cannot read pad data
}
fclose(pad_file);
return 0; // Success
}
// Universal ASCII armor generator - consolidates duplicate ASCII armor generation
// Creates ASCII armored output format used by both text and file encryption
int generate_ascii_armor(const char* chksum, uint64_t offset, const unsigned char* encrypted_data,
size_t data_length, char** ascii_output) {
if (!chksum || !encrypted_data || !ascii_output) {
return 1; // Error: null pointer
}
// Encode data as base64
char* base64_data = custom_base64_encode(encrypted_data, data_length);
if (!base64_data) {
return 2; // Error: base64 encoding failed
}
// Calculate required buffer size
size_t base64_len = strlen(base64_data);
size_t header_size = 200; // Approximate size for headers
size_t total_size = header_size + base64_len + (base64_len / 64) + 100; // +newlines +footer
*ascii_output = malloc(total_size);
if (!*ascii_output) {
free(base64_data);
return 3; // Error: memory allocation failed
}
// Build ASCII armor
strcpy(*ascii_output, "-----BEGIN OTP MESSAGE-----\n");
char temp_line[256];
snprintf(temp_line, sizeof(temp_line), "Version: v0.3.15\n");
strcat(*ascii_output, temp_line);
snprintf(temp_line, sizeof(temp_line), "Pad-ChkSum: %s\n", chksum);
strcat(*ascii_output, temp_line);
snprintf(temp_line, sizeof(temp_line), "Pad-Offset: %lu\n", offset);
strcat(*ascii_output, temp_line);
strcat(*ascii_output, "\n");
// Add base64 data in 64-character lines
int b64_len = strlen(base64_data);
for (int i = 0; i < b64_len; i += 64) {
char line[70];
snprintf(line, sizeof(line), "%.64s\n", base64_data + i);
strcat(*ascii_output, line);
}
strcat(*ascii_output, "-----END OTP MESSAGE-----\n");
free(base64_data);
return 0; // Success
}
// Universal pad integrity validator - consolidates pad validation logic
// Verifies pad checksum matches expected value for security
int validate_pad_integrity(const char* pad_path, const char* expected_chksum) {
if (!pad_path || !expected_chksum) {
return 1; // Error: null pointer
}
char current_chksum[MAX_HASH_LENGTH];
if (calculate_checksum(pad_path, current_chksum) != 0) {
return 2; // Error: cannot calculate checksum
}
if (strcmp(expected_chksum, current_chksum) != 0) {
return 3; // Error: checksum mismatch
}
return 0; // Success - pad integrity verified
}
// Universal decrypt function - consolidates all decrypt operations
// input_data: encrypted message text or file path
// output_target: output file path (NULL for stdout/interactive)
// mode: determines behavior and output format
int universal_decrypt(const char* input_data, const char* output_target, decrypt_mode_t mode) {
char stored_chksum[MAX_HASH_LENGTH];
uint64_t pad_offset;
char base64_data[MAX_INPUT_SIZE * 8] = {0};
unsigned char* ciphertext = NULL;
int ciphertext_len;
// Handle input based on mode
if (mode == DECRYPT_MODE_FILE_TO_TEXT || mode == DECRYPT_MODE_FILE_TO_FILE) {
// File input - read the entire file
FILE* input_fp = fopen(input_data, "r");
if (!input_fp) {
printf("Error: Cannot open input file %s\n", input_data);
return 1;
}
fseek(input_fp, 0, SEEK_END);
long file_size = ftell(input_fp);
fseek(input_fp, 0, SEEK_SET);
char* file_content = malloc(file_size + 1);
if (!file_content) {
printf("Error: Memory allocation failed\n");
fclose(input_fp);
return 1;
}
size_t bytes_read = fread(file_content, 1, file_size, input_fp);
file_content[bytes_read] = '\0';
fclose(input_fp);
// Parse ASCII message from file content
if (parse_ascii_message(file_content, stored_chksum, &pad_offset, base64_data) != 0) {
printf("Error: Invalid ASCII armored format in file\n");
free(file_content);
return 1;
}
free(file_content);
if (mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Decrypting ASCII armored file...\n");
}
// Note: DECRYPT_MODE_FILE_TO_FILE should be completely silent for piping
} else {
// Text input (interactive or piped)
const char* message_text;
char full_message[MAX_INPUT_SIZE * 4] = {0};
if (input_data != NULL) {
message_text = input_data;
} else {
// Interactive mode - read from stdin
if (mode == DECRYPT_MODE_INTERACTIVE) {
printf("Enter encrypted message (paste the full ASCII armor block):\n");
}
char line[MAX_LINE_LENGTH];
while (fgets(line, sizeof(line), stdin)) {
strncat(full_message, line, sizeof(full_message) - strlen(full_message) - 1);
if (strstr(line, "-----END OTP MESSAGE-----")) {
break;
}
}
message_text = full_message;
}
// Parse ASCII message from text
if (parse_ascii_message(message_text, stored_chksum, &pad_offset, base64_data) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Invalid message format - missing BEGIN header\n");
} else {
printf("Error: Invalid message format - missing BEGIN header\n");
}
return 1;
}
}
// Get pad path and check existence
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
get_pad_path(stored_chksum, pad_path, state_path);
if (access(pad_path, R_OK) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Required pad not found: %s\n", stored_chksum);
} else {
printf("Error: Required pad not found: %s\n", stored_chksum);
if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0);
if (selected) {
free(selected);
}
}
}
return 1;
}
// Validate pad integrity
int integrity_result = validate_pad_integrity(pad_path, stored_chksum);
if (integrity_result == 3) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Pad integrity check failed!\n");
return 1;
} else if (mode == DECRYPT_MODE_INTERACTIVE) {
printf("Warning: Pad integrity check failed!\n");
printf("Expected: %s\n", stored_chksum);
printf("Continue anyway? (y/N): ");
fflush(stdout);
char response[10];
if (fgets(response, sizeof(response), stdin) == NULL ||
(response[0] != 'y' && response[0] != 'Y')) {
printf("Decryption aborted.\n");
return 1;
}
}
} else if (integrity_result != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Cannot verify pad integrity\n");
} else {
printf("Error: Cannot verify pad integrity\n");
}
return 1;
} else {
if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Pad integrity: VERIFIED\n");
}
}
// Decode base64 ciphertext
ciphertext = custom_base64_decode(base64_data, &ciphertext_len);
if (!ciphertext) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Invalid base64 data\n");
} else {
printf("Error: Invalid base64 data\n");
}
return 1;
}
// Load pad data using universal function
unsigned char* pad_data;
if (load_pad_data(stored_chksum, pad_offset, ciphertext_len, &pad_data) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Cannot load pad data\n");
} else {
printf("Error: Cannot load pad data\n");
}
free(ciphertext);
return 1;
}
// Decrypt using universal XOR operation
if (universal_xor_operation(ciphertext, ciphertext_len, pad_data, ciphertext) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Decryption operation failed\n");
} else {
printf("Error: Decryption operation failed\n");
}
free(ciphertext);
free(pad_data);
return 1;
}
// Output based on mode
if (mode == DECRYPT_MODE_FILE_TO_FILE) {
// Write to output file
const char* output_file = output_target;
// Generate default output filename if not provided
char default_output[512];
if (output_file == NULL) {
strncpy(default_output, input_data, sizeof(default_output) - 1);
default_output[sizeof(default_output) - 1] = '\0';
char* ext = strstr(default_output, ".otp.asc");
if (ext) {
*ext = '\0';
} else {
strncat(default_output, ".decrypted", sizeof(default_output) - strlen(default_output) - 1);
}
output_file = default_output;
}
FILE* output_fp = fopen(output_file, "wb");
if (!output_fp) {
printf("Error: Cannot create output file %s\n", output_file);
free(ciphertext);
free(pad_data);
return 1;
}
if (fwrite(ciphertext, 1, ciphertext_len, output_fp) != (size_t)ciphertext_len) {
printf("Error: Cannot write decrypted data\n");
free(ciphertext);
free(pad_data);
fclose(output_fp);
return 1;
}
fclose(output_fp);
// Only show success messages in non-silent modes
if (mode != DECRYPT_MODE_FILE_TO_FILE) {
printf("File decrypted successfully: %s\n", output_file);
printf("Note: ASCII format does not preserve original filename/permissions\n");
}
} else {
// Text output to stdout - need to allocate space for null terminator
char* decrypted_text = malloc(ciphertext_len + 1);
if (!decrypted_text) {
printf("Error: Memory allocation failed for output\n");
free(ciphertext);
free(pad_data);
return 1;
}
memcpy(decrypted_text, ciphertext, ciphertext_len);
decrypted_text[ciphertext_len] = '\0';
if (mode == DECRYPT_MODE_SILENT) {
// Silent mode - just output the text
printf("%s\n", decrypted_text);
fflush(stdout);
} else {
// Interactive mode - with label
printf("Decrypted: %s\n", decrypted_text);
}
free(decrypted_text);
}
// Cleanup
free(ciphertext);
free(pad_data);
return 0;
}
// Enhanced input function with editable pre-filled text
int get_filename_with_default(const char* prompt, const char* default_path, char* result, size_t result_size) {
// Find the last directory separator
char* last_slash = strrchr(default_path, '/');
char directory[1024] = "";
char filename[512] = "";
if (last_slash) {
// Extract directory path
size_t dir_len = last_slash - default_path + 1; // Include the trailing slash
if (dir_len < sizeof(directory)) {
strncpy(directory, default_path, dir_len);
directory[dir_len] = '\0';
}
// Extract filename
strncpy(filename, last_slash + 1, sizeof(filename) - 1);
filename[sizeof(filename) - 1] = '\0';
} else {
// No directory separator, treat as filename only
strncpy(filename, default_path, sizeof(filename) - 1);
filename[sizeof(filename) - 1] = '\0';
}
// Setup terminal for raw input
struct termios orig_termios;
if (tcgetattr(STDIN_FILENO, &orig_termios) != 0) {
// Fallback to simple input if terminal control fails
printf("\n%s\n%s: ", prompt, directory);
fflush(stdout);
char input_buffer[512];
if (!fgets(input_buffer, sizeof(input_buffer), stdin)) {
return 1;
}
input_buffer[strcspn(input_buffer, "\n")] = 0;
if (strlen(input_buffer) == 0) {
strncpy(result, default_path, result_size - 1);
} else {
if (strlen(directory) > 0) {
snprintf(result, result_size, "%s%s", directory, input_buffer);
} else {
strncpy(result, input_buffer, result_size - 1);
}
}
result[result_size - 1] = '\0';
return 0;
}
// Set up raw terminal mode
struct termios raw_termios = orig_termios;
raw_termios.c_lflag &= ~(ECHO | ICANON);
raw_termios.c_cc[VMIN] = 1;
raw_termios.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSANOW, &raw_termios) != 0) {
// Fallback if terminal setup fails
printf("\n%s\n%s: ", prompt, directory);
fflush(stdout);
char input_buffer[512];
if (!fgets(input_buffer, sizeof(input_buffer), stdin)) {
return 1;
}
input_buffer[strcspn(input_buffer, "\n")] = 0;
if (strlen(input_buffer) == 0) {
strncpy(result, default_path, result_size - 1);
} else {
if (strlen(directory) > 0) {
snprintf(result, result_size, "%s%s", directory, input_buffer);
} else {
strncpy(result, input_buffer, result_size - 1);
}
}
result[result_size - 1] = '\0';
return 0;
}
// Display prompt and directory
printf("\n%s\n%s", prompt, directory);
fflush(stdout);
// Initialize editing buffer with default filename
char edit_buffer[512];
strncpy(edit_buffer, filename, sizeof(edit_buffer) - 1);
edit_buffer[sizeof(edit_buffer) - 1] = '\0';
int cursor_pos = strlen(edit_buffer);
int buffer_len = cursor_pos;
// Display initial filename
printf("%s", edit_buffer);
fflush(stdout);
// Main editing loop
int c;
while ((c = getchar()) != EOF) {
if (c == '\n' || c == '\r') {
// Enter key - accept input
break;
} else if (c == 127 || c == 8) {
// Backspace/Delete
if (cursor_pos > 0) {
// Move everything after cursor one position left
memmove(&edit_buffer[cursor_pos - 1], &edit_buffer[cursor_pos], buffer_len - cursor_pos + 1);
cursor_pos--;
buffer_len--;
// Redraw line: move cursor to start of filename area, clear to end, redraw buffer
printf("\r%s\033[K%s", directory, edit_buffer);
// Position cursor correctly
if (cursor_pos < buffer_len) {
printf("\033[%dD", buffer_len - cursor_pos);
}
fflush(stdout);
}
} else if (c == 27) {
// Escape sequence (arrow keys, etc.)
int c1 = getchar();
int c2 = getchar();
if (c1 == '[') {
if (c2 == 'C' && cursor_pos < buffer_len) {
// Right arrow
cursor_pos++;
printf("\033[1C");
fflush(stdout);
} else if (c2 == 'D' && cursor_pos > 0) {
// Left arrow
cursor_pos--;
printf("\033[1D");
fflush(stdout);
} else if (c2 == 'H') {
// Home key
printf("\033[%dD", cursor_pos);
cursor_pos = 0;
fflush(stdout);
} else if (c2 == 'F') {
// End key
printf("\033[%dC", buffer_len - cursor_pos);
cursor_pos = buffer_len;
fflush(stdout);
}
}
} else if (c >= 32 && c <= 126) {
// Printable character
if (buffer_len < (int)sizeof(edit_buffer) - 1) {
// Move everything after cursor one position right
memmove(&edit_buffer[cursor_pos + 1], &edit_buffer[cursor_pos], buffer_len - cursor_pos + 1);
edit_buffer[cursor_pos] = c;
cursor_pos++;
buffer_len++;
// Redraw from cursor position
printf("%c", c);
if (cursor_pos < buffer_len) {
// Print remaining characters and move cursor back
printf("%s\033[%dD", &edit_buffer[cursor_pos], buffer_len - cursor_pos);
}
fflush(stdout);
}
}
// Ignore other control characters
}
// Restore terminal
tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
printf("\n");
// Construct final result
if (buffer_len == 0) {
// Empty input, use default
strncpy(result, default_path, result_size - 1);
} else {
// Combine directory with edited filename
edit_buffer[buffer_len] = '\0';
if (strlen(directory) > 0) {
snprintf(result, result_size, "%s%s", directory, edit_buffer);
} else {
strncpy(result, edit_buffer, result_size - 1);
}
}
result[result_size - 1] = '\0';
return 0;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// EDITOR AND FILE MANAGER
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
char* get_preferred_editor(void) {
// Check EDITOR environment variable first
char* editor = getenv("EDITOR");
if (editor && strlen(editor) > 0) {
// Verify the editor exists
char command[512];
snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", editor);
if (system(command) == 0) {
return strdup(editor);
}
}
// Check VISUAL environment variable
editor = getenv("VISUAL");
if (editor && strlen(editor) > 0) {
char command[512];
snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", editor);
if (system(command) == 0) {
return strdup(editor);
}
}
// Try common editors in order of preference
const char* common_editors[] = {"vim", "vi", "nano", "emacs", "gedit", NULL};
for (int i = 0; common_editors[i] != NULL; i++) {
char command[512];
snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", common_editors[i]);
if (system(command) == 0) {
return strdup(common_editors[i]);
}
}
return NULL; // No editor found
}
char* get_preferred_file_manager(void) {
// Try file managers in order of preference
const char* file_managers[] = {"ranger", "fzf", "nnn", "lf", NULL};
for (int i = 0; file_managers[i] != NULL; i++) {
char command[512];
snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", file_managers[i]);
if (system(command) == 0) {
return strdup(file_managers[i]);
}
}
return NULL; // No file manager found
}
int launch_text_editor(const char* initial_content, char* result_buffer, size_t buffer_size) {
char* editor = get_preferred_editor();
if (!editor) {
printf("Error: No text editor found. Set EDITOR environment variable or install vim/nano.\n");
return 1;
}
// Create temporary file
char temp_filename[64];
snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_edit_%ld.tmp", time(NULL));
// Write initial content to temp file if provided
if (initial_content && strlen(initial_content) > 0) {
FILE* temp_file = fopen(temp_filename, "w");
if (temp_file) {
fputs(initial_content, temp_file);
fclose(temp_file);
}
} else {
// Create empty temp file
FILE* temp_file = fopen(temp_filename, "w");
if (temp_file) {
fclose(temp_file);
}
}
// Launch editor
printf("Opening %s for text editing...\n", editor);
char command[512];
snprintf(command, sizeof(command), "%s %s", editor, temp_filename);
int result = system(command);
if (result == 0) {
// Read the edited content back
FILE* temp_file = fopen(temp_filename, "r");
if (temp_file) {
size_t bytes_read = fread(result_buffer, 1, buffer_size - 1, temp_file);
result_buffer[bytes_read] = '\0';
// Remove trailing newline if present
if (bytes_read > 0 && result_buffer[bytes_read - 1] == '\n') {
result_buffer[bytes_read - 1] = '\0';
}
fclose(temp_file);
} else {
printf("Error: Cannot read edited content\n");
free(editor);
unlink(temp_filename);
return 1;
}
} else {
printf("Editor exited with error or was cancelled\n");
free(editor);
unlink(temp_filename);
return 1;
}
// Clean up
unlink(temp_filename);
free(editor);
return 0;
}
int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size) {
char* fm = get_preferred_file_manager();
if (!fm) {
printf("No file manager found. Please install ranger, fzf, nnn, or lf.\n");
printf("Falling back to manual file path entry.\n");
return 1; // Fall back to manual entry
}
char temp_filename[64];
snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_file_%ld.tmp", time(NULL));
char command[512];
int result = 1;
printf("Opening %s for file selection...\n", fm);
if (strcmp(fm, "ranger") == 0) {
snprintf(command, sizeof(command), "cd '%s' && ranger --choosefile=%s",
start_directory ? start_directory : ".", temp_filename);
} else if (strcmp(fm, "fzf") == 0) {
snprintf(command, sizeof(command), "cd '%s' && find . -type f | fzf > %s",
start_directory ? start_directory : ".", temp_filename);
} else if (strcmp(fm, "nnn") == 0) {
snprintf(command, sizeof(command), "cd '%s' && nnn -p %s",
start_directory ? start_directory : ".", temp_filename);
} else if (strcmp(fm, "lf") == 0) {
snprintf(command, sizeof(command), "cd '%s' && lf -selection-path=%s",
start_directory ? start_directory : ".", temp_filename);
}
result = system(command);
if (result == 0 || result == 256) { // Some file managers return 256 on success
// Read selected file from temp file
FILE* temp_file = fopen(temp_filename, "r");
if (temp_file) {
if (fgets(selected_file, buffer_size, temp_file)) {
// Remove trailing newline
selected_file[strcspn(selected_file, "\n\r")] = 0;
// For relative paths from fzf, make absolute if needed
if (selected_file[0] == '.' && selected_file[1] == '/') {
char current_dir[512];
if (getcwd(current_dir, sizeof(current_dir))) {
char abs_path[1024];
snprintf(abs_path, sizeof(abs_path), "%s/%s", current_dir, selected_file + 2);
strncpy(selected_file, abs_path, buffer_size - 1);
selected_file[buffer_size - 1] = '\0';
}
}
fclose(temp_file);
unlink(temp_filename);
free(fm);
return 0; // Success
}
fclose(temp_file);
}
}
// Clean up and indicate failure
unlink(temp_filename);
free(fm);
return 1; // Fall back to manual entry
}
int handle_text_encrypt(void) {
printf("\n");
print_centered_header("Text Encrypt", 0);
// Launch text editor directly
char text_buffer[MAX_INPUT_SIZE];
if (launch_text_editor(NULL, text_buffer, sizeof(text_buffer)) != 0) {
printf("Error: Could not launch text editor\n");
return 1;
}
if (strlen(text_buffer) == 0) {
printf("No text entered - canceling encryption\n");
return 1;
}
// Use unified pad selection
char* selected_pad = select_pad_interactive("Select Pad for Text Encryption",
"Select pad (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("Text encryption cancelled.\n");
return 1;
}
int result = encrypt_text(selected_pad, text_buffer);
free(selected_pad);
return result;
}
int handle_file_encrypt(void) {
printf("\n");
print_centered_header("File Encrypt", 0);
// Launch file manager directly
char input_file[512];
if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) {
printf("Error: Could not launch file manager\n");
return 1;
}
// Check if file exists
if (access(input_file, R_OK) != 0) {
printf("Error: File '%s' not found or cannot be read\n", input_file);
return 1;
}
// Use unified pad selection
char* selected_pad = select_pad_interactive("Select Pad for File Encryption",
"Select pad (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("File encryption cancelled.\n");
return 1;
}
// Ask for output format
printf("\nSelect output format:\n");
printf("1. Binary (.otp) - preserves file permissions\n");
printf("2. ASCII (.otp.asc) - text-safe format\n");
printf("Enter choice (1-2): ");
char format_input[10];
if (!fgets(format_input, sizeof(format_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
int ascii_armor = (atoi(format_input) == 2) ? 1 : 0;
// Generate default output filename
char default_output[1024]; // Increased buffer size to prevent truncation warnings
if (ascii_armor) {
snprintf(default_output, sizeof(default_output), "%s.otp.asc", input_file);
} else {
snprintf(default_output, sizeof(default_output), "%s.otp", input_file);
}
// Use enhanced input function for output filename
char output_file[512];
if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) {
printf("Error: Failed to read input\n");
return 1;
}
const char* output_filename = output_file;
int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor);
free(selected_pad);
return result;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// PADS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int calculate_checksum(const char* filename, char* checksum_hex) {
FILE* file = fopen(filename, "rb");
if (!file) {
return 1;
}
unsigned char checksum[32];
unsigned char buffer[64 * 1024]; // 64KB buffer for large files
size_t bytes_read;
// Initialize checksum
memset(checksum, 0, 32);
size_t total_bytes = 0;
// Calculate XOR checksum of entire file
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
// Process this chunk with XOR checksum
for (size_t i = 0; i < bytes_read; i++) {
unsigned char bucket = (total_bytes + i) % 32;
checksum[bucket] ^= buffer[i] ^ (((total_bytes + i) >> 8) & 0xFF) ^
(((total_bytes + i) >> 16) & 0xFF) ^ (((total_bytes + i) >> 24) & 0xFF);
}
total_bytes += bytes_read;
}
fclose(file);
// Now encrypt the checksum with the first 32 bytes of the pad
fseek(file = fopen(filename, "rb"), 0, SEEK_SET);
unsigned char pad_key[32];
if (fread(pad_key, 1, 32, file) != 32) {
fclose(file);
return 1;
}
fclose(file);
// XOR encrypt the checksum with pad data to create unique identifier
unsigned char encrypted_checksum[32];
for (int i = 0; i < 32; i++) {
encrypted_checksum[i] = checksum[i] ^ pad_key[i];
}
// Convert to hex string (64 characters)
for (int i = 0; i < 32; i++) {
sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]);
}
checksum_hex[64] = '\0';
return 0;
}
// Calculate checksum with progress display for large files
int calculate_checksum_with_progress(const char* filename, char* checksum_hex, int display_progress, uint64_t file_size) {
FILE* file = fopen(filename, "rb");
if (!file) {
return 1;
}
unsigned char checksum[32];
unsigned char buffer[64 * 1024]; // 64KB buffer for large files
size_t bytes_read;
// Initialize checksum
memset(checksum, 0, 32);
size_t total_bytes = 0;
time_t start_time = time(NULL);
// Calculate XOR checksum of entire file with progress
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
// Process this chunk with XOR checksum
for (size_t i = 0; i < bytes_read; i++) {
unsigned char bucket = (total_bytes + i) % 32;
checksum[bucket] ^= buffer[i] ^ (((total_bytes + i) >> 8) & 0xFF) ^
(((total_bytes + i) >> 16) & 0xFF) ^ (((total_bytes + i) >> 24) & 0xFF);
}
total_bytes += bytes_read;
// Show progress for large files (every 64MB or if display_progress is enabled)
if (display_progress && file_size > 10 * 1024 * 1024 && total_bytes % (64 * 1024 * 1024) == 0) {
show_progress(total_bytes, file_size, start_time);
}
}
// Final progress update
if (display_progress && file_size > 10 * 1024 * 1024) {
show_progress(file_size, file_size, start_time);
printf("\n");
}
fclose(file);
// Now encrypt the checksum with the first 32 bytes of the pad
fseek(file = fopen(filename, "rb"), 0, SEEK_SET);
unsigned char pad_key[32];
if (fread(pad_key, 1, 32, file) != 32) {
fclose(file);
return 1;
}
fclose(file);
// XOR encrypt the checksum with pad data to create unique identifier
unsigned char encrypted_checksum[32];
for (int i = 0; i < 32; i++) {
encrypted_checksum[i] = checksum[i] ^ pad_key[i];
}
// Convert to hex string (64 characters)
for (int i = 0; i < 32; i++) {
sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]);
}
checksum_hex[64] = '\0';
return 0;
}
// Unified pad selection function - extracts the best UI from handle_pads_menu()
char* select_pad_interactive(const char* title, const char* prompt, pad_filter_type_t filter_type, int allow_cancel) {
// Get list of pads from current directory
DIR* dir = opendir(current_pads_dir);
if (!dir) {
printf("Error: Cannot open pads directory %s\n", current_pads_dir);
return NULL;
}
// Structure to store pad information
struct PadInfo {
char chksum[65];
char size_str[32];
char used_str[32];
double percentage;
char location[256];
};
struct PadInfo pads[100]; // Support up to 100 pads
int pad_count = 0;
// Collect all pad information
struct dirent* entry;
while ((entry = readdir(dir)) != NULL && pad_count < 100) {
if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) {
strncpy(pads[pad_count].chksum, entry->d_name, 64);
pads[pad_count].chksum[64] = '\0';
// Get pad file size and usage info
char full_path[1024];
snprintf(full_path, sizeof(full_path), "%s/%s", current_pads_dir, entry->d_name);
struct stat st;
if (stat(full_path, &st) == 0) {
// Get used bytes from state
uint64_t used_bytes;
read_state_offset(pads[pad_count].chksum, &used_bytes);
// Apply filter
if (filter_type == PAD_FILTER_UNUSED_ONLY && used_bytes > 32) {
continue; // Skip used pads when filtering for unused only
}
// Format total size
if (st.st_size < 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%luB", st.st_size);
} else if (st.st_size < 1024 * 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fKB", (double)st.st_size / 1024.0);
} else if (st.st_size < 1024 * 1024 * 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fMB", (double)st.st_size / (1024.0 * 1024.0));
} else {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.2fGB", (double)st.st_size / (1024.0 * 1024.0 * 1024.0));
}
// Format used size
if (used_bytes < 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%luB", used_bytes);
} else if (used_bytes < 1024 * 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fKB", (double)used_bytes / 1024.0);
} else if (used_bytes < 1024 * 1024 * 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fMB", (double)used_bytes / (1024.0 * 1024.0));
} else {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.2fGB", (double)used_bytes / (1024.0 * 1024.0 * 1024.0));
}
// Calculate percentage
pads[pad_count].percentage = (double)used_bytes / st.st_size * 100.0;
// Set location info using directory display
get_directory_display(full_path, pads[pad_count].location, sizeof(pads[pad_count].location));
pad_count++;
}
}
}
closedir(dir);
if (pad_count == 0) {
printf("\n%s\n", title);
if (filter_type == PAD_FILTER_UNUSED_ONLY) {
printf("No unused pads found.\n");
printf("Entropy can only be added to pads with 0%% usage (only reserved bytes used).\n");
} else {
printf("No pads found.\n");
}
return NULL;
}
// Calculate minimal unique prefixes for each pad
char prefixes[100][65];
int prefix_lengths[100];
for (int i = 0; i < pad_count; i++) {
prefix_lengths[i] = 1;
// Find minimal unique prefix
while (prefix_lengths[i] <= 64) {
int unique = 1;
// Check if current prefix is unique among all other pads
for (int j = 0; j < pad_count; j++) {
if (i != j && strncmp(pads[i].chksum, pads[j].chksum, prefix_lengths[i]) == 0) {
unique = 0;
break;
}
}
if (unique) {
break;
}
prefix_lengths[i]++;
}
// Store the minimal prefix
strncpy(prefixes[i], pads[i].chksum, prefix_lengths[i]);
prefixes[i][prefix_lengths[i]] = '\0';
}
// Display title and pads table
printf("\n%s\n", title);
printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "ChkSum", "D", "Dir", "Size", "Used", "% Used");
printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "--------", "--", "------------", "----------", "----------", "------");
// Get current default pad path for comparison
char* current_default = get_default_pad_path();
char default_pad_checksum[65] = "";
if (current_default) {
// Extract checksum from default pad path
char* filename = strrchr(current_default, '/');
if (!filename) filename = current_default;
else filename++; // Skip the '/'
// Extract checksum (remove .pad extension)
if (strlen(filename) >= 68 && strstr(filename, ".pad")) {
strncpy(default_pad_checksum, filename, 64);
default_pad_checksum[64] = '\0';
}
free(current_default);
}
for (int i = 0; i < pad_count; i++) {
// Check if this is the default pad
int is_default = (strlen(default_pad_checksum) > 0 &&
strncmp(pads[i].chksum, default_pad_checksum, 64) == 0);
// Display first 8 characters of checksum with prefix underlined
char checksum_8char[9];
strncpy(checksum_8char, pads[i].chksum, 8);
checksum_8char[8] = '\0';
printf("\033[4m%.*s\033[0m%s %-2s %-12s %-12s %-12s %.1f%%\n",
prefix_lengths[i], checksum_8char, // Underlined prefix
checksum_8char + prefix_lengths[i], // Rest of 8-char checksum
is_default ? "*" : "", // Default indicator
pads[i].location,
pads[i].size_str,
pads[i].used_str,
pads[i].percentage);
}
// Display prompt
printf("\n%s", prompt);
if (allow_cancel) {
printf(" (or 'x' to cancel)");
}
printf(": ");
char input[MAX_HASH_LENGTH];
if (!fgets(input, sizeof(input), stdin)) {
printf("Error: Failed to read input\n");
return NULL;
}
input[strcspn(input, "\n")] = 0;
// Handle empty input - select default pad if available
if (strlen(input) == 0) {
// Get current default pad path
char* current_default = get_default_pad_path();
if (current_default) {
// Extract checksum from default pad path
char* filename = strrchr(current_default, '/');
if (!filename) filename = current_default;
else filename++; // Skip the '/'
// Extract checksum (remove .pad extension)
if (strlen(filename) >= 68 && strstr(filename, ".pad")) {
char default_checksum[65];
strncpy(default_checksum, filename, 64);
default_checksum[64] = '\0';
// Verify this default pad is in our current list
for (int i = 0; i < pad_count; i++) {
if (strncmp(pads[i].chksum, default_checksum, 64) == 0) {
free(current_default);
printf("Selected default pad: %.16s...\n\n", default_checksum);
return strdup(default_checksum);
}
}
}
free(current_default);
}
// No default pad or default pad not in current list
printf("No default pad available or default pad not in current list\n");
return NULL;
}
// Handle cancel
if (allow_cancel && (toupper(input[0]) == 'X' && strlen(input) == 1)) {
return NULL;
}
// Find matching pad by prefix only
int selected_pad = -1;
int match_count = 0;
// Try prefix matching only
for (int i = 0; i < pad_count; i++) {
if (strncmp(input, pads[i].chksum, strlen(input)) == 0) {
if (match_count == 0) {
selected_pad = i;
}
match_count++;
}
}
if (match_count == 0) {
printf("No pad found matching '%s'\n", input);
return NULL;
} else if (match_count > 1) {
printf("Ambiguous prefix. Multiple matches found.\n");
return NULL;
}
// Return selected pad checksum (caller must free)
return strdup(pads[selected_pad].chksum);
}
int handle_pads_menu(void) {
printf("\n");
print_centered_header("Pad Management", 0);
// Get list of pads from current directory
DIR* dir = opendir(current_pads_dir);
if (!dir) {
printf("Error: Cannot open pads directory %s\n", current_pads_dir);
return 1;
}
// Structure to store pad information
struct PadInfo {
char chksum[65];
char size_str[32];
char used_str[32];
double percentage;
char location[256]; // Store location info
};
struct PadInfo pads[100]; // Support up to 100 pads
int pad_count = 0;
// Collect all pad information
struct dirent* entry;
while ((entry = readdir(dir)) != NULL && pad_count < 100) {
if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) {
strncpy(pads[pad_count].chksum, entry->d_name, 64);
pads[pad_count].chksum[64] = '\0';
// Get pad file size and usage info
char full_path[1024]; // Increased buffer size
snprintf(full_path, sizeof(full_path), "%s/%s", current_pads_dir, entry->d_name);
struct stat st;
if (stat(full_path, &st) == 0) {
// Get used bytes from state
uint64_t used_bytes;
read_state_offset(pads[pad_count].chksum, &used_bytes);
// Format total size
if (st.st_size < 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%luB", st.st_size);
} else if (st.st_size < 1024 * 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fKB", (double)st.st_size / 1024.0);
} else if (st.st_size < 1024 * 1024 * 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fMB", (double)st.st_size / (1024.0 * 1024.0));
} else {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.2fGB", (double)st.st_size / (1024.0 * 1024.0 * 1024.0));
}
// Format used size
if (used_bytes < 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%luB", used_bytes);
} else if (used_bytes < 1024 * 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fKB", (double)used_bytes / 1024.0);
} else if (used_bytes < 1024 * 1024 * 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fMB", (double)used_bytes / (1024.0 * 1024.0));
} else {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.2fGB", (double)used_bytes / (1024.0 * 1024.0 * 1024.0));
}
// Calculate percentage
pads[pad_count].percentage = (double)used_bytes / st.st_size * 100.0;
// Set location info using directory display
get_directory_display(full_path, pads[pad_count].location, sizeof(pads[pad_count].location));
pad_count++;
}
}
}
closedir(dir);
if (pad_count == 0) {
printf("No pads found.\n");
printf("\nOptions:\n");
printf(" \033[4mG\033[0menerate new pad\n");
printf(" E\033[4mx\033[0mit\n");
printf("\nSelect option: ");
char input[10];
if (fgets(input, sizeof(input), stdin)) {
char choice = toupper(input[0]);
if (choice == 'G') {
int result = handle_generate_menu();
if (result == 0) {
// After successful pad generation, return to pads menu
return handle_pads_menu();
}
return result;
}
}
return 0;
}
// Calculate minimal unique prefixes for each pad
char prefixes[100][65]; // Store the minimal prefix for each pad
int prefix_lengths[100]; // Length of minimal prefix for each pad
for (int i = 0; i < pad_count; i++) {
prefix_lengths[i] = 1;
// Find minimal unique prefix
while (prefix_lengths[i] <= 64) {
int unique = 1;
// Check if current prefix is unique among all other pads
for (int j = 0; j < pad_count; j++) {
if (i != j && strncmp(pads[i].chksum, pads[j].chksum, prefix_lengths[i]) == 0) {
unique = 0;
break;
}
}
if (unique) {
break;
}
prefix_lengths[i]++;
}
// Store the minimal prefix
strncpy(prefixes[i], pads[i].chksum, prefix_lengths[i]);
prefixes[i][prefix_lengths[i]] = '\0';
}
// Display pads with minimal prefixes underlined and default indicator
printf("\nAvailable pads:\n");
printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "ChkSum", "D", "Dir", "Size", "Used", "% Used");
printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "--------", "--", "------------", "----------", "----------", "------");
// Get current default pad path for comparison
char* current_default = get_default_pad_path();
char default_pad_checksum[65] = "";
if (current_default) {
// Extract checksum from default pad path
char* filename = strrchr(current_default, '/');
if (!filename) filename = current_default;
else filename++; // Skip the '/'
// Extract checksum (remove .pad extension)
if (strlen(filename) >= 68 && strstr(filename, ".pad")) {
strncpy(default_pad_checksum, filename, 64);
default_pad_checksum[64] = '\0';
}
free(current_default);
}
for (int i = 0; i < pad_count; i++) {
// Check if this is the default pad
int is_default = (strlen(default_pad_checksum) > 0 &&
strncmp(pads[i].chksum, default_pad_checksum, 64) == 0);
// Display first 8 characters of checksum with prefix underlined
char checksum_8char[9];
strncpy(checksum_8char, pads[i].chksum, 8);
checksum_8char[8] = '\0';
printf("\033[4m%.*s\033[0m%s %-2s %-12s %-12s %-12s %.1f%%\n",
prefix_lengths[i], checksum_8char, // Underlined prefix
checksum_8char + prefix_lengths[i], // Rest of 8-char checksum
is_default ? "*" : "", // Default indicator
pads[i].location, // Use the stored location info
pads[i].size_str,
pads[i].used_str,
pads[i].percentage);
}
printf("\nActions:\n");
printf(" \033[4mG\033[0menerate new pad\n");
printf(" \033[4mA\033[0mdd entropy to pad\n");
printf(" \033[4mV\033[0merify pad integrity\n");
printf(" \033[4mD\033[0melete pad\n");
printf(" \033[4mS\033[0met default pad\n");
printf(" E\033[4mx\033[0mit\n");
printf("\nSelect action: ");
char input[MAX_HASH_LENGTH];
if (!fgets(input, sizeof(input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
input[strcspn(input, "\n")] = 0;
// Handle actions
if (toupper(input[0]) == 'G') {
int result = handle_generate_menu();
if (result == 0) {
// After successful pad generation, return to pads menu
return handle_pads_menu();
}
return result;
} else if (toupper(input[0]) == 'A') {
// Add entropy to pad - use unified function with unused pads filter
char* selected_pad = select_pad_interactive("Select Unused Pad for Entropy Addition",
"Select unused pad (by prefix)",
PAD_FILTER_UNUSED_ONLY, 1);
if (!selected_pad) {
printf("Entropy addition cancelled.\n");
return handle_pads_menu();
}
// Add entropy to the selected unused pad
int result = handle_add_entropy_to_pad(selected_pad);
free(selected_pad);
if (result == 0) {
// Return to pads menu after successful entropy addition
return handle_pads_menu();
}
return result;
} else if (toupper(input[0]) == 'V') {
// Verify pad integrity - use unified function
char* selected_pad = select_pad_interactive("Select Pad for Verification",
"Select pad to verify (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("Pad verification cancelled.\n");
return handle_pads_menu();
}
// Verify the selected pad
handle_verify_pad(selected_pad);
free(selected_pad);
return handle_pads_menu(); // Always return to pads menu after verification
} else if (toupper(input[0]) == 'D') {
// Delete pad - use unified function
char* selected_pad = select_pad_interactive("Select Pad for Deletion",
"Select pad to delete (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("Pad deletion cancelled.\n");
return handle_pads_menu();
}
// Delete the selected pad
handle_delete_pad(selected_pad);
free(selected_pad);
return handle_pads_menu(); // Always return to pads menu after deletion attempt
} else if (toupper(input[0]) == 'S') {
// Set default pad - use unified function
char* selected_pad = select_pad_interactive("Select Default Pad",
"Select pad to set as default (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("Default pad selection cancelled.\n");
return handle_pads_menu();
}
// Construct the full absolute pad path and set as default
char new_default_path[1024];
if (current_pads_dir[0] == '/') {
// Already absolute path
int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad);
if (ret >= (int)sizeof(new_default_path)) {
printf("Error: Path too long for default pad setting\n");
free(selected_pad);
return handle_pads_menu();
}
} else {
// Relative path - make it absolute
char current_dir[512];
if (getcwd(current_dir, sizeof(current_dir))) {
int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s/%s.pad", current_dir, current_pads_dir, selected_pad);
if (ret >= (int)sizeof(new_default_path)) {
// Path was truncated, fall back to relative path
int ret2 = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad);
if (ret2 >= (int)sizeof(new_default_path)) {
printf("Error: Path too long for default pad setting\n");
free(selected_pad);
return handle_pads_menu();
}
}
} else {
// Fallback to relative path
int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad);
if (ret >= (int)sizeof(new_default_path)) {
printf("Error: Path too long for default pad setting\n");
free(selected_pad);
return handle_pads_menu();
}
}
}
if (set_default_pad_path(new_default_path) == 0) {
printf("Default pad set to: %.16s...\n", selected_pad);
printf("Full path: %s\n", new_default_path);
} else {
printf("Error: Failed to update default pad preference\n");
}
free(selected_pad);
return handle_pads_menu();
} else if (toupper(input[0]) == 'X') {
return 0; // Exit to main menu
} else {
printf("Invalid action. Please select G, A, S, or X.\n");
return handle_pads_menu();
}
}
int handle_add_entropy_to_pad(const char* pad_chksum) {
char header_text[128];
snprintf(header_text, sizeof(header_text), "Add Entropy to Pad: %.16s...", pad_chksum);
printf("\n");
print_centered_header(header_text, 0);
// Present entropy source selection menu with consistent formatting
printf("Select entropy source:\n");
printf(" \033[4mK\033[0meyboard entropy - Random typing for entropy collection\n");
printf(" \033[4mD\033[0mice rolls - Manual dice roll input for high-quality entropy\n");
printf(" \033[4mT\033[0mrueRNG devices - Hardware random number generators\n");
printf(" E\033[4mx\033[0mit\n");
printf("\nSelect option: ");
char source_input[10];
if (!fgets(source_input, sizeof(source_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
char choice = toupper(source_input[0]);
entropy_source_t entropy_source;
switch (choice) {
case 'K':
entropy_source = ENTROPY_SOURCE_KEYBOARD;
break;
case 'D':
entropy_source = ENTROPY_SOURCE_DICE;
break;
case 'T':
entropy_source = ENTROPY_SOURCE_TRUERNG;
break;
case 'X':
return 0; // Exit
default:
printf("Invalid choice. Please select K, D, T, or X.\n");
return 1;
}
size_t target_bytes;
// For TrueRNG, automatically use the full pad size
if (entropy_source == ENTROPY_SOURCE_TRUERNG) {
// Get the pad file size
char pad_path[1024];
char state_path[1024];
get_pad_path(pad_chksum, pad_path, state_path);
struct stat pad_stat;
if (stat(pad_path, &pad_stat) != 0) {
printf("Error: Cannot get pad file size\n");
return 1;
}
target_bytes = (size_t)pad_stat.st_size;
printf("\nTrueRNG selected - will enhance entire pad with hardware entropy\n");
printf("Pad size: %.2f GB (%zu bytes)\n",
(double)target_bytes / (1024.0 * 1024.0 * 1024.0), target_bytes);
} else {
// For other entropy sources, show the selection menu
printf("\nEntropy collection options:\n");
printf(" 1. Recommended (2048 bytes) - Optimal security\n");
printf(" 2. Minimum (1024 bytes) - Good security\n");
printf(" 3. Maximum (4096 bytes) - Maximum security\n");
printf(" 4. Custom amount\n");
printf("Enter choice (1-4): ");
char amount_input[10];
if (!fgets(amount_input, sizeof(amount_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
target_bytes = 2048; // Default
int amount_choice = atoi(amount_input);
switch (amount_choice) {
case 1:
target_bytes = 2048;
break;
case 2:
target_bytes = 1024;
break;
case 3:
target_bytes = 4096;
break;
case 4:
printf("Enter custom amount (512-8192 bytes): ");
char custom_input[32];
if (!fgets(custom_input, sizeof(custom_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
size_t custom_amount = (size_t)atoi(custom_input);
if (custom_amount < 512 || custom_amount > 8192) {
printf("Error: Invalid amount. Must be between 512 and 8192 bytes.\n");
return 1;
}
target_bytes = custom_amount;
break;
default:
target_bytes = 2048; // Default to recommended
break;
}
}
// For TrueRNG with large pads, use streaming approach
if (entropy_source == ENTROPY_SOURCE_TRUERNG && target_bytes > MAX_ENTROPY_BUFFER) {
printf("\nUsing streaming approach for large pad enhancement...\n");
int result = collect_truerng_entropy_streaming(pad_chksum, target_bytes, 1);
if (result != 0) {
printf("Error: TrueRNG streaming entropy collection failed\n");
return 1;
}
// Update checksum after entropy addition
printf("\n🔄 Updating pad checksum...\n");
char new_chksum[65];
int checksum_result = update_pad_checksum_after_entropy(pad_chksum, new_chksum);
if (checksum_result == 0) {
printf("✓ Pad checksum updated successfully\n");
printf(" Old checksum: %.16s...\n", pad_chksum);
printf(" New checksum: %.16s...\n", new_chksum);
printf("✓ Pad files renamed to new checksum\n");
} else if (checksum_result == 2) {
printf(" Checksum unchanged (unusual but not an error)\n");
} else {
printf("⚠ Warning: Checksum update failed (entropy was added successfully)\n");
printf(" You may need to manually handle the checksum update\n");
return 1;
}
printf("\n🎉 SUCCESS! Your entire pad now has enhanced randomness!\n");
// Use enhanced pause mechanism instead of simple getchar
print_centered_header("Pad Enhancement Complete", 1);
return 0;
}
// For other entropy sources or smaller amounts, use traditional approach
printf("\nCollecting %zu bytes of entropy from selected source...\n", target_bytes);
// Allocate entropy buffer
unsigned char* entropy_buffer = malloc(MAX_ENTROPY_BUFFER);
if (!entropy_buffer) {
printf("Error: Cannot allocate entropy buffer\n");
return 1;
}
// Collect entropy using unified interface
size_t collected_bytes = 0;
int result = collect_entropy_by_source(entropy_source, entropy_buffer, target_bytes, &collected_bytes, 1);
if (result != 0) {
printf("Error: Entropy collection failed\n");
free(entropy_buffer);
return 1;
}
if (collected_bytes < 512) {
printf("Error: Insufficient entropy collected (%zu bytes)\n", collected_bytes);
free(entropy_buffer);
return 1;
}
printf("\nProcessing entropy and modifying pad...\n");
// Add entropy to pad
result = add_entropy_to_pad(pad_chksum, entropy_buffer, collected_bytes, 1);
// Clear entropy buffer for security
memset(entropy_buffer, 0, MAX_ENTROPY_BUFFER);
free(entropy_buffer);
if (result != 0) {
printf("Error: Failed to add entropy to pad\n");
return 1;
}
printf("\n🎉 SUCCESS! Your pad now has enhanced randomness!\n");
// Use enhanced pause mechanism instead of simple getchar
print_centered_header("Entropy Enhancement Complete", 1);
return 0;
}
int handle_verify_pad(const char* pad_chksum) {
char header_text[128];
snprintf(header_text, sizeof(header_text), "Verify Pad Integrity: %.16s...", pad_chksum);
printf("\n");
print_centered_header(header_text, 0);
// Construct pad file path
char pad_path[1024];
char state_path[1024];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if pad file exists
if (access(pad_path, R_OK) != 0) {
printf("❌ ERROR: Pad file not found: %s\n", pad_path);
return 1;
}
printf("Calculating pad checksum...\n");
// Calculate actual checksum of the pad file
char calculated_checksum[65];
if (calculate_checksum(pad_path, calculated_checksum) != 0) {
printf("❌ ERROR: Failed to calculate pad checksum\n");
return 1;
}
// Compare calculated checksum with filename (expected checksum)
if (strcmp(pad_chksum, calculated_checksum) == 0) {
printf("✅ SUCCESS: Pad integrity verified!\n");
printf(" Expected: %.16s...\n", pad_chksum);
printf(" Actual: %.16s...\n", calculated_checksum);
printf(" Status: MATCH - Pad is intact and valid\n");
// Get additional pad info
struct stat pad_stat;
if (stat(pad_path, &pad_stat) == 0) {
uint64_t used_bytes;
read_state_offset(pad_chksum, &used_bytes);
double size_gb = (double)pad_stat.st_size / (1024.0 * 1024.0 * 1024.0);
double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0);
double usage_percent = (double)used_bytes / pad_stat.st_size * 100.0;
printf(" Size: %.2f GB (%lu bytes)\n", size_gb, pad_stat.st_size);
printf(" Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes);
printf(" Usage: %.1f%%\n", usage_percent);
}
printf("\n✅ This pad is safe to use for encryption.\n");
// Pause before returning to menu to let user see the verification results
print_centered_header("Pad Verification Complete", 1);
return 0;
} else {
printf("❌ FAILURE: Pad integrity check failed!\n");
printf(" Expected: %.16s...\n", pad_chksum);
printf(" Actual: %.16s...\n", calculated_checksum);
printf(" Status: MISMATCH - Pad may be corrupted!\n");
printf("\n⚠️ WARNING: This pad should NOT be used for encryption.\n");
printf(" The pad file may have been modified or corrupted.\n");
printf(" Consider regenerating this pad or using a different one.\n");
return 1;
}
}
int handle_delete_pad(const char* pad_chksum) {
char header_text[128];
snprintf(header_text, sizeof(header_text), "Delete Pad: %.16s...", pad_chksum);
printf("\n");
print_centered_header(header_text, 0);
// Construct pad and state file paths
char pad_path[1024];
char state_path[1024];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if pad file exists
if (access(pad_path, F_OK) != 0) {
printf("❌ ERROR: Pad file not found: %s\n", pad_path);
return 1;
}
// Get pad information for display
struct stat pad_stat;
if (stat(pad_path, &pad_stat) == 0) {
uint64_t used_bytes;
read_state_offset(pad_chksum, &used_bytes);
double size_gb = (double)pad_stat.st_size / (1024.0 * 1024.0 * 1024.0);
double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0);
double usage_percent = (double)used_bytes / pad_stat.st_size * 100.0;
printf("Pad Information:\n");
printf(" Checksum: %s\n", pad_chksum);
printf(" Size: %.2f GB (%lu bytes)\n", size_gb, pad_stat.st_size);
printf(" Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes);
printf(" Usage: %.1f%%\n", usage_percent);
printf(" Path: %s\n", pad_path);
}
// Check if this is the default pad
char* current_default = get_default_pad_path();
int is_default_pad = 0;
if (current_default) {
// Check if the pad to be deleted is the current default
if (strstr(current_default, pad_chksum)) {
is_default_pad = 1;
printf(" Status: ⚠️ This is your DEFAULT pad\n");
}
free(current_default);
}
// Warning and confirmation
printf("\n⚠️ WARNING: This action cannot be undone!\n");
if (is_default_pad) {
printf("⚠️ Deleting the default pad will require setting a new default.\n");
}
printf("\nAre you absolutely sure you want to delete this pad? (y/N): ");
char response[10];
if (!fgets(response, sizeof(response), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
// Require explicit 'y' or 'Y' to proceed
if (response[0] != 'y' && response[0] != 'Y') {
printf("Pad deletion cancelled.\n");
return 0; // User cancelled - not an error
}
// Double confirmation for extra safety
printf("\nFinal confirmation - type 'DELETE' to proceed: ");
char final_response[20];
if (!fgets(final_response, sizeof(final_response), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
// Remove newline
final_response[strcspn(final_response, "\n")] = 0;
if (strcmp(final_response, "DELETE") != 0) {
printf("Confirmation text did not match. Pad deletion cancelled.\n");
return 0; // User didn't confirm - not an error
}
// Proceed with deletion
printf("\nDeleting pad files...\n");
// Delete pad file
if (unlink(pad_path) != 0) {
printf("❌ ERROR: Failed to delete pad file: %s\n", pad_path);
perror("unlink");
return 1;
} else {
printf("✅ Deleted pad file: %s\n", pad_path);
}
// Delete state file (if it exists)
if (access(state_path, F_OK) == 0) {
if (unlink(state_path) != 0) {
printf("⚠️ WARNING: Failed to delete state file: %s\n", state_path);
perror("unlink");
// Continue - pad file was deleted successfully
} else {
printf("✅ Deleted state file: %s\n", state_path);
}
} else {
printf(" No state file found (this is normal)\n");
}
// Handle default pad update if necessary
if (is_default_pad) {
printf("\n🔄 Updating default pad preference...\n");
// Clear the current default pad
if (set_preference("default_pad", NULL) == 0) {
printf("✅ Default pad preference cleared\n");
printf(" You can set a new default pad from the pad management menu\n");
} else {
printf("⚠️ WARNING: Failed to clear default pad preference\n");
printf(" You may need to manually update your configuration\n");
}
}
printf("\n✅ SUCCESS: Pad deleted successfully!\n");
printf(" Checksum: %.16s...\n", pad_chksum);
printf(" Both pad and state files have been removed\n");
// Pause before returning to menu to let user see the success message
print_centered_header("Pad Deletion Complete", 1);
return 0;
}
void print_usage(const char* program_name) {
printf("OTP Cipher - One Time Pad Implementation v0.3.15\n");
printf("Built for testing entropy system\n");
printf("Usage:\n");
printf(" %s - Interactive mode\n", program_name);
printf(" %s generate|-g <size> - 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 <file> <pad_prefix> [-a] [-o <out>] - Encrypt file\n", program_name);
printf(" %s list|-l - List available pads\n", program_name);
printf("\nFile Operations:\n");
printf(" -f <file> <pad> - Encrypt file (binary .otp format)\n");
printf(" -f <file> <pad> -a - Encrypt file (ASCII .otp.asc format)\n");
printf(" -o <output> - 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");
}