Compare commits

...

17 Commits

Author SHA1 Message Date
dce0a74945 Version v0.3.50 - Fixing browsing behavior 2026-01-28 06:54:57 -04:00
d8e65b1799 Version v0.3.49 - Working on directory naviagation 2026-01-27 10:24:23 -04:00
1e017d81b7 Fixing user interface problems 2026-01-17 07:39:23 -04:00
2e2f78720e Added exponential padding to increase security. 2026-01-13 03:13:54 -05:00
302b200548 Version v0.3.48 - -m Fix directory encryption output filename - strip trailing slash from directory path 2025-12-27 12:18:08 -05:00
18a4441746 Version v0.3.47 - -m Fix directory decryption - skip pause for temp files to allow extraction to continue 2025-12-27 12:13:57 -05:00
974470238d Version v0.3.46 - -m Fix directory decryption default filename - remove .tar.gz.otp extension 2025-12-27 12:10:14 -05:00
7c2821dd0d Version v0.3.45 - -m Fix directory encryption output path - save in same directory as source 2025-12-27 12:03:27 -05:00
6f3976bc07 Version v0.3.44 - -m Disable pad integrity check during decryption for performance - trust filename checksum 2025-12-27 11:59:37 -05:00
3bef639cc3 Version v0.3.43 - -m Remove unnecessary calculate_checksum() calls during encryption - use filename checksum directly 2025-12-27 11:56:28 -05:00
81eded2995 Version v0.3.42 - -m Suppress miniz warnings in archive.c header include 2025-12-27 11:51:10 -05:00
e126e30889 Version v0.3.41 - -m Suppress miniz library warnings, auto-select pad when only one available 2025-12-27 11:49:29 -05:00
6fe12e0c1c Version v0.3.40 - -m Add directory encryption (TAR+GZIP+OTP), integrate ranger for directory selection, add microtar/miniz libraries, remove binary state file backward compatibility - enforce text format only 2025-12-27 11:45:31 -05:00
89aa3baff6 Version v0.3.39 - . 2025-12-25 08:25:18 -05:00
39e818dd51 Version v0.3.38 - Implement ChaCha20 nonce extension to support pads larger than 256GB 2025-12-24 10:00:32 -05:00
977da58a3b Version v0.3.37 - Implement ChaCha20 nonce extension to support pads larger than 256GB 2025-12-24 10:00:27 -05:00
2c311a9a61 Version v0.3.36 - "." 2025-12-21 09:18:39 -04:00
20 changed files with 2031 additions and 221 deletions

7
.gitignore vendored
View File

@@ -24,3 +24,10 @@ test_truerng
# Temporary files # Temporary files
*.pad *.pad
*.state *.state
# Downloaded dependencies (source)
miniz/
microtar/
# Test directories
test_dir/

View File

@@ -1,25 +1,40 @@
CC = gcc CC = gcc
CFLAGS = -Wall -Wextra -std=c99 -Isrc CFLAGS = -Wall -Wextra -std=c99 -Isrc -Iminiz -Imicrotar/src
CFLAGS_MINIZ = -Wall -Wextra -std=c99 -D_POSIX_C_SOURCE=200112L -Isrc -Iminiz -Imicrotar/src -Wno-unused-function -Wno-implicit-function-declaration
LIBS = -lm LIBS = -lm
LIBS_STATIC = -static -lm LIBS_STATIC = -static -lm
ARCH = $(shell uname -m) ARCH = $(shell uname -m)
TARGET = build/otp-$(ARCH) TARGET = build/otp-$(ARCH)
SOURCES = $(wildcard src/*.c) SOURCES = $(wildcard src/*.c)
MINIZ_SOURCES = $(wildcard miniz/*.c)
MICROTAR_SOURCES = $(wildcard microtar/src/*.c)
OBJS = $(SOURCES:.c=.o) OBJS = $(SOURCES:.c=.o)
MINIZ_OBJS = $(MINIZ_SOURCES:.c=.o)
MICROTAR_OBJS = $(MICROTAR_SOURCES:.c=.o)
ALL_OBJS = $(OBJS) $(MINIZ_OBJS) $(MICROTAR_OBJS)
# Default build target # Default build target
$(TARGET): $(OBJS) $(TARGET): $(ALL_OBJS)
@mkdir -p build @mkdir -p build
$(CC) $(CFLAGS) -o $(TARGET) $(OBJS) $(LIBS) $(CC) $(CFLAGS) -o $(TARGET) $(ALL_OBJS) $(LIBS)
@rm -f $(OBJS) @rm -f $(ALL_OBJS)
# Static linking target # Static linking target
static: $(OBJS) static: $(ALL_OBJS)
@mkdir -p build @mkdir -p build
$(CC) $(CFLAGS) -o $(TARGET) $(OBJS) $(LIBS_STATIC) $(CC) $(CFLAGS) -o $(TARGET) $(ALL_OBJS) $(LIBS_STATIC)
@rm -f $(OBJS) @rm -f $(ALL_OBJS)
%.o: %.c # Compile main source files with full warnings
src/%.o: src/%.c
$(CC) $(CFLAGS) -c $< -o $@
# Compile miniz library files with reduced warnings
miniz/%.o: miniz/%.c
$(CC) $(CFLAGS_MINIZ) -c $< -o $@
# Compile microtar library files normally
microtar/src/%.o: microtar/src/%.c
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) -c $< -o $@
clean: clean:

View File

@@ -43,6 +43,7 @@ One-time pads can be trivially encrypted and decrypted using pencil and paper, m
## Features ## Features
- **Perfect Security**: Implements true one-time pad encryption with information-theoretic security - **Perfect Security**: Implements true one-time pad encryption with information-theoretic security
- **Traffic Analysis Resistance**: Exponential bucketing with ISO/IEC 9797-1 Method 2 (Padmé) padding hides message lengths
- **Text & File Encryption**: Supports both inline text and file encryption - **Text & File Encryption**: Supports both inline text and file encryption
- **Multiple Output Formats**: Binary (.otp) and ASCII armored (.otp.asc) file formats - **Multiple Output Formats**: Binary (.otp) and ASCII armored (.otp.asc) file formats
- **Hardware RNG Support**: Direct entropy collection from TrueRNG USB devices with automatic detection - **Hardware RNG Support**: Direct entropy collection from TrueRNG USB devices with automatic detection
@@ -58,14 +59,14 @@ One-time pads can be trivially encrypted and decrypted using pencil and paper, m
### Download Pre-Built Binaries ### Download Pre-Built Binaries
**[Download Current Linux x86](https://git.laantungir.net/laantungir/otp/releases/download/v0.3.34/otp-v0.3.34-linux-x86_64)** **[Download Current Linux x86](https://git.laantungir.net/laantungir/otp/releases/download/v0.3.49/otp-v0.3.49-linux-x86_64)**
**[Download Current Raspberry Pi 64](https://git.laantungir.net/laantungir/otp/releases/download/v0.3.34/otp-v0.3.34-linux-arm64)** **[Download Current Raspberry Pi 64](https://git.laantungir.net/laantungir/otp/releases/download/v0.3.49/otp-v0.3.49-linux-arm64)**
After downloading: After downloading:
```bash ```bash
# Rename for convenience, then make executable # Rename for convenience, then make executable
mv otp-v0.3.34-linux-x86_64 otp mv otp-v0.3.49-linux-x86_64 otp
chmod +x otp chmod +x otp
# Run it # Run it
@@ -189,8 +190,23 @@ git tag v1.0.0 # Next build: v1.0.1
- Custom 256-bit XOR checksum for pad identification (encrypted with pad data) - Custom 256-bit XOR checksum for pad identification (encrypted with pad data)
- Read-only pad files to prevent accidental modification - Read-only pad files to prevent accidental modification
- State tracking to prevent pad reuse - State tracking to prevent pad reuse
- **Message Length Hiding**: Exponential bucketing (256B, 512B, 1KB, 2KB, 4KB...) prevents traffic analysis
- **ISO/IEC 9797-1 Method 2 Padding**: Standard-compliant Padmé padding with 0x80 marker
- **Zero external crypto dependencies** - completely self-contained implementation - **Zero external crypto dependencies** - completely self-contained implementation
### Message Padding
All encrypted messages and files are automatically padded using exponential bucketing to resist traffic analysis attacks:
- **Minimum size**: 256 bytes
- **Bucket sizes**: 256B → 512B → 1KB → 2KB → 4KB → 8KB → ...
- **Padding method**: ISO/IEC 9797-1 Method 2 (Padmé padding)
- Appends `0x80` byte after message
- Fills remaining space with `0x00` bytes
- Unambiguous padding removal during decryption
**Example**: A 10-byte message is padded to 256 bytes, while a 300-byte message is padded to 512 bytes. This provides strong protection for small messages where length leakage matters most, with logarithmic overhead for larger messages.
## Project Structure ## Project Structure
``` ```
@@ -205,6 +221,7 @@ otp/
│ ├── ui.c # Interactive user interface and menu system │ ├── ui.c # Interactive user interface and menu system
│ ├── state.c # Global state management (pads directory, preferences) │ ├── state.c # Global state management (pads directory, preferences)
│ ├── crypto.c # Core cryptographic operations (XOR, base64) │ ├── crypto.c # Core cryptographic operations (XOR, base64)
│ ├── padding.c # Message padding (exponential bucketing, Padmé padding)
│ ├── pads.c # Pad management and file operations │ ├── pads.c # Pad management and file operations
│ ├── entropy.c # Entropy collection from various sources │ ├── entropy.c # Entropy collection from various sources
│ ├── trng.c # Hardware RNG device detection and collection │ ├── trng.c # Hardware RNG device detection and collection
@@ -217,6 +234,7 @@ otp/
├── pads/ # OTP pad storage directory (created at runtime) ├── pads/ # OTP pad storage directory (created at runtime)
├── files/ # Encrypted file storage (created at runtime) ├── files/ # Encrypted file storage (created at runtime)
└── tests/ # Test scripts and utilities └── tests/ # Test scripts and utilities
└── test_padding.sh # Padding implementation tests
``` ```
## Architecture ## Architecture
@@ -227,6 +245,7 @@ The OTP cipher uses a modular architecture with clean separation of concerns:
- **ui.c**: Interactive user interface, menus, and terminal management - **ui.c**: Interactive user interface, menus, and terminal management
- **state.c**: Global state management (pads directory, terminal dimensions, preferences) - **state.c**: Global state management (pads directory, terminal dimensions, preferences)
- **crypto.c**: Core cryptographic operations (XOR encryption, base64 encoding) - **crypto.c**: Core cryptographic operations (XOR encryption, base64 encoding)
- **padding.c**: Message padding implementation (exponential bucketing, ISO/IEC 9797-1 Method 2)
- **pads.c**: Pad file management, checksums, and state tracking - **pads.c**: Pad file management, checksums, and state tracking
- **entropy.c**: Entropy collection from keyboard, dice, files, and hardware RNG - **entropy.c**: Entropy collection from keyboard, dice, files, and hardware RNG
- **trng.c**: Hardware RNG device detection and entropy collection from USB devices - **trng.c**: Hardware RNG device detection and entropy collection from USB devices
@@ -431,6 +450,28 @@ No. ChkSum (first 16 chars) Size Used % Used
# Select "S" for show pad info, enter checksum or prefix # Select "S" for show pad info, enter checksum or prefix
``` ```
## Important Notes
### Size Units: Decimal (SI) vs Binary (IEC)
**This program uses decimal (SI) units for all size specifications**, matching the behavior of most system tools like `ls -lh`, `df -h`, and file managers:
- **1 KB** = 1,000 bytes (not 1,024)
- **1 MB** = 1,000,000 bytes (not 1,048,576)
- **1 GB** = 1,000,000,000 bytes (not 1,073,741,824)
- **1 TB** = 1,000,000,000,000 bytes (not 1,099,511,627,776)
**Why decimal units?**
- Consistency with system tools (`ls`, `df`, file managers)
- Matches storage device marketing (a "1TB" USB drive has ~1,000,000,000,000 bytes)
- Avoids confusion when comparing sizes across different tools
- Industry standard for storage devices and file systems
**Example:** When you request a 100GB pad, the program creates exactly 100,000,000,000 bytes, which will display as "100GB" in `ls -lh` and your file manager.
**Note:** Some technical tools may use binary units (GiB, MiB) where 1 GiB = 1,024³ bytes. This program intentionally uses decimal units for user-friendliness and consistency with common tools.
## License ## License
This project includes automatic versioning system based on the Generic Automatic Version Increment System. This project includes automatic versioning system based on the Generic Automatic Version Increment System.

View File

@@ -346,10 +346,29 @@ clean_project() {
} }
install_project() { install_project() {
print_status "Installing OTP project..." print_status "Building project before installation..."
# Build the project first (without version increment for install)
print_status "Cleaning previous build..."
make clean
print_status "Building OTP project for x86_64..."
make CC=gcc ARCH=x86_64
if [ $? -ne 0 ]; then
print_error "Build failed"
return 1
fi
print_success "Build completed successfully"
# Clean up object files after successful build
print_status "Cleaning up object files..."
rm -f src/*.o miniz/*.o microtar/src/*.o
# Now install
print_status "Installing OTP project to system..."
make install make install
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
print_success "Installation completed" print_success "Installation completed - binary installed to /usr/local/bin/otp"
else else
print_error "Installation failed" print_error "Installation failed"
return 1 return 1

BIN
dir_nav Executable file

Binary file not shown.

1
otp
View File

@@ -1 +0,0 @@
./build/otp-x86_64

504
src/archive.c Normal file
View File

@@ -0,0 +1,504 @@
#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 <dirent.h>
#include <time.h>
#include "main.h"
#include "microtar.h"
// Suppress warnings from miniz header
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#include "miniz.h"
#pragma GCC diagnostic pop
////////////////////////////////////////////////////////////////////////////////
// DIRECTORY ARCHIVING FUNCTIONS
////////////////////////////////////////////////////////////////////////////////
// Helper function to recursively add directory contents to TAR archive
static int add_directory_to_tar(mtar_t* tar, const char* base_path, const char* relative_path) {
DIR* dir = opendir(base_path);
if (!dir) {
printf("Error: Cannot open directory '%s'\n", base_path);
return 1;
}
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
// Skip . and ..
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
// Build full path
char full_path[2048];
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
// Build relative path for TAR
char tar_path[2048];
if (strlen(relative_path) > 0) {
snprintf(tar_path, sizeof(tar_path), "%s/%s", relative_path, entry->d_name);
} else {
snprintf(tar_path, sizeof(tar_path), "%s", entry->d_name);
}
struct stat st;
if (stat(full_path, &st) != 0) {
printf("Warning: Cannot stat '%s', skipping\n", full_path);
continue;
}
if (S_ISDIR(st.st_mode)) {
// Recursively add subdirectory
if (add_directory_to_tar(tar, full_path, tar_path) != 0) {
closedir(dir);
return 1;
}
} else if (S_ISREG(st.st_mode)) {
// Add regular file
FILE* fp = fopen(full_path, "rb");
if (!fp) {
printf("Warning: Cannot open '%s', skipping\n", full_path);
continue;
}
// Get file size
fseek(fp, 0, SEEK_END);
size_t file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// Read file data
unsigned char* file_data = malloc(file_size);
if (!file_data) {
printf("Error: Memory allocation failed for '%s'\n", full_path);
fclose(fp);
closedir(dir);
return 1;
}
size_t bytes_read = fread(file_data, 1, file_size, fp);
fclose(fp);
if (bytes_read != file_size) {
printf("Warning: Could not read entire file '%s', skipping\n", full_path);
free(file_data);
continue;
}
// Write to TAR
if (mtar_write_file_header(tar, tar_path, file_size) != MTAR_ESUCCESS) {
printf("Error: Failed to write TAR header for '%s'\n", tar_path);
free(file_data);
closedir(dir);
return 1;
}
if (mtar_write_data(tar, file_data, file_size) != MTAR_ESUCCESS) {
printf("Error: Failed to write TAR data for '%s'\n", tar_path);
free(file_data);
closedir(dir);
return 1;
}
free(file_data);
}
}
closedir(dir);
return 0;
}
// Create TAR archive from directory
int create_tar_archive(const char* dir_path, const char* tar_output_path) {
mtar_t tar;
if (mtar_open(&tar, tar_output_path, "w") != MTAR_ESUCCESS) {
printf("Error: Cannot create TAR file '%s'\n", tar_output_path);
return 1;
}
// Get directory name for relative paths
char dir_name[512];
const char* last_slash = strrchr(dir_path, '/');
if (last_slash) {
strncpy(dir_name, last_slash + 1, sizeof(dir_name) - 1);
} else {
strncpy(dir_name, dir_path, sizeof(dir_name) - 1);
}
dir_name[sizeof(dir_name) - 1] = '\0';
// Add directory contents to TAR
int result = add_directory_to_tar(&tar, dir_path, dir_name);
// Finalize and close TAR
mtar_finalize(&tar);
mtar_close(&tar);
return result;
}
// Extract TAR archive to directory
int extract_tar_archive(const char* tar_path, const char* output_dir) {
mtar_t tar;
mtar_header_t header;
if (mtar_open(&tar, tar_path, "r") != MTAR_ESUCCESS) {
printf("Error: Cannot open TAR file '%s'\n", tar_path);
return 1;
}
// Create output directory if it doesn't exist
mkdir(output_dir, 0755);
// Extract each file
while (mtar_read_header(&tar, &header) == MTAR_ESUCCESS) {
char output_path[2048];
snprintf(output_path, sizeof(output_path), "%s/%s", output_dir, header.name);
// Create parent directories
char* last_slash = strrchr(output_path, '/');
if (last_slash) {
char parent_dir[2048];
strncpy(parent_dir, output_path, last_slash - output_path);
parent_dir[last_slash - output_path] = '\0';
// Create directories recursively
char* p = parent_dir;
while (*p) {
if (*p == '/') {
*p = '\0';
mkdir(parent_dir, 0755);
*p = '/';
}
p++;
}
mkdir(parent_dir, 0755);
}
// Extract file data
unsigned char* data = malloc(header.size);
if (!data) {
printf("Error: Memory allocation failed\n");
mtar_close(&tar);
return 1;
}
if (mtar_read_data(&tar, data, header.size) != MTAR_ESUCCESS) {
printf("Error: Failed to read data for '%s'\n", header.name);
free(data);
mtar_close(&tar);
return 1;
}
// Write to file
FILE* fp = fopen(output_path, "wb");
if (!fp) {
printf("Error: Cannot create file '%s'\n", output_path);
free(data);
mtar_close(&tar);
return 1;
}
fwrite(data, 1, header.size, fp);
fclose(fp);
free(data);
mtar_next(&tar);
}
mtar_close(&tar);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// COMPRESSION FUNCTIONS
////////////////////////////////////////////////////////////////////////////////
// Compress file with gzip (miniz)
int compress_file_gzip(const char* input_path, const char* output_path) {
// Read input file
FILE* in = fopen(input_path, "rb");
if (!in) {
printf("Error: Cannot open input file '%s'\n", input_path);
return 1;
}
fseek(in, 0, SEEK_END);
size_t input_size = ftell(in);
fseek(in, 0, SEEK_SET);
unsigned char* input_data = malloc(input_size);
if (!input_data) {
printf("Error: Memory allocation failed\n");
fclose(in);
return 1;
}
size_t bytes_read = fread(input_data, 1, input_size, in);
fclose(in);
if (bytes_read != input_size) {
printf("Error: Failed to read input file\n");
free(input_data);
return 1;
}
// Compress with miniz
mz_ulong compressed_size = compressBound(input_size);
unsigned char* compressed_data = malloc(compressed_size);
if (!compressed_data) {
printf("Error: Memory allocation failed\n");
free(input_data);
return 1;
}
int result = compress2(compressed_data, &compressed_size,
input_data, input_size,
MZ_BEST_COMPRESSION);
free(input_data);
if (result != MZ_OK) {
printf("Error: Compression failed (error code: %d)\n", result);
free(compressed_data);
return 1;
}
// Write compressed data
FILE* out = fopen(output_path, "wb");
if (!out) {
printf("Error: Cannot create output file '%s'\n", output_path);
free(compressed_data);
return 1;
}
fwrite(compressed_data, 1, compressed_size, out);
fclose(out);
free(compressed_data);
return 0;
}
// Decompress gzip file (miniz)
int decompress_file_gzip(const char* input_path, const char* output_path) {
// Read compressed file
FILE* in = fopen(input_path, "rb");
if (!in) {
printf("Error: Cannot open compressed file '%s'\n", input_path);
return 1;
}
fseek(in, 0, SEEK_END);
size_t compressed_size = ftell(in);
fseek(in, 0, SEEK_SET);
unsigned char* compressed_data = malloc(compressed_size);
if (!compressed_data) {
printf("Error: Memory allocation failed\n");
fclose(in);
return 1;
}
size_t bytes_read = fread(compressed_data, 1, compressed_size, in);
fclose(in);
if (bytes_read != compressed_size) {
printf("Error: Failed to read compressed file\n");
free(compressed_data);
return 1;
}
// Estimate decompressed size (try multiple times if needed)
mz_ulong output_size = compressed_size * 10;
unsigned char* output_data = NULL;
int result;
for (int attempt = 0; attempt < 3; attempt++) {
output_data = realloc(output_data, output_size);
if (!output_data) {
printf("Error: Memory allocation failed\n");
free(compressed_data);
return 1;
}
mz_ulong temp_size = output_size;
result = uncompress(output_data, &temp_size, compressed_data, compressed_size);
if (result == MZ_OK) {
output_size = temp_size;
break;
} else if (result == MZ_BUF_ERROR) {
// Buffer too small, try larger
output_size *= 2;
} else {
printf("Error: Decompression failed (error code: %d)\n", result);
free(compressed_data);
free(output_data);
return 1;
}
}
free(compressed_data);
if (result != MZ_OK) {
printf("Error: Decompression failed after multiple attempts\n");
free(output_data);
return 1;
}
// Write decompressed data
FILE* out = fopen(output_path, "wb");
if (!out) {
printf("Error: Cannot create output file '%s'\n", output_path);
free(output_data);
return 1;
}
fwrite(output_data, 1, output_size, out);
fclose(out);
free(output_data);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// HIGH-LEVEL DIRECTORY ENCRYPTION/DECRYPTION
////////////////////////////////////////////////////////////////////////////////
// Encrypt directory: TAR → GZIP → Encrypt
int encrypt_directory(const char* dir_path, const char* pad_identifier, const char* output_file) {
char temp_tar[512];
char temp_gz[512];
int result = 0;
// Generate temporary file paths
snprintf(temp_tar, sizeof(temp_tar), "/tmp/otp_tar_%d.tar", getpid());
snprintf(temp_gz, sizeof(temp_gz), "/tmp/otp_gz_%d.tar.gz", getpid());
printf("Creating TAR archive...\n");
if (create_tar_archive(dir_path, temp_tar) != 0) {
printf("Error: Failed to create TAR archive\n");
return 1;
}
printf("Compressing archive...\n");
if (compress_file_gzip(temp_tar, temp_gz) != 0) {
printf("Error: Failed to compress archive\n");
unlink(temp_tar);
return 1;
}
printf("Encrypting compressed archive...\n");
result = encrypt_file(pad_identifier, temp_gz, output_file, 0);
// Cleanup temporary files
unlink(temp_tar);
unlink(temp_gz);
if (result == 0) {
printf("Directory encrypted successfully: %s\n", output_file);
}
return result;
}
// Detect if file is a compressed TAR archive
int is_compressed_tar_archive(const char* file_path) {
FILE* fp = fopen(file_path, "rb");
if (!fp) {
return 0;
}
unsigned char magic[512];
size_t bytes_read = fread(magic, 1, sizeof(magic), fp);
fclose(fp);
if (bytes_read < 2) {
return 0;
}
// Check for GZIP magic bytes (0x1f 0x8b)
if (magic[0] == 0x1f && magic[1] == 0x8b) {
return 1;
}
// Check for TAR magic ("ustar" at offset 257)
if (bytes_read >= 262 && memcmp(magic + 257, "ustar", 5) == 0) {
return 1;
}
return 0;
}
// Decrypt and extract directory: Decrypt → GUNZIP → Extract TAR
int decrypt_and_extract_directory(const char* encrypted_file, const char* output_dir) {
char temp_decrypted[512];
char temp_tar[512];
int result = 0;
// Generate temporary file paths
snprintf(temp_decrypted, sizeof(temp_decrypted), "/tmp/otp_decrypt_%d", getpid());
snprintf(temp_tar, sizeof(temp_tar), "/tmp/otp_tar_%d.tar", getpid());
printf("Decrypting file...\n");
if (decrypt_file(encrypted_file, temp_decrypted) != 0) {
printf("Error: Failed to decrypt file\n");
return 1;
}
// Check if it's compressed
FILE* fp = fopen(temp_decrypted, "rb");
if (!fp) {
printf("Error: Cannot open decrypted file\n");
unlink(temp_decrypted);
return 1;
}
unsigned char magic[2];
fread(magic, 1, 2, fp);
fclose(fp);
if (magic[0] == 0x1f && magic[1] == 0x8b) {
// GZIP compressed
printf("Decompressing archive...\n");
if (decompress_file_gzip(temp_decrypted, temp_tar) != 0) {
printf("Error: Failed to decompress archive\n");
unlink(temp_decrypted);
return 1;
}
unlink(temp_decrypted);
} else {
// Not compressed, assume it's already TAR
printf("File is not compressed, using as TAR directly...\n");
if (rename(temp_decrypted, temp_tar) != 0) {
printf("Error: Failed to rename decrypted file to TAR file\n");
unlink(temp_decrypted);
return 1;
}
}
// Verify TAR file exists before extraction
if (access(temp_tar, F_OK) != 0) {
printf("Error: TAR file does not exist at '%s'\n", temp_tar);
return 1;
}
printf("Extracting archive...\n");
result = extract_tar_archive(temp_tar, output_dir);
// Cleanup
unlink(temp_tar);
if (result == 0) {
printf("Directory extracted successfully to: %s\n", output_dir);
}
return result;
}

View File

@@ -297,7 +297,6 @@ int encrypt_text(const char* pad_identifier, const char* input_text) {
} }
char text_buffer[MAX_INPUT_SIZE]; char text_buffer[MAX_INPUT_SIZE];
char chksum_hex[MAX_HASH_LENGTH];
uint64_t current_offset; uint64_t current_offset;
char pad_path[MAX_HASH_LENGTH + 20]; char pad_path[MAX_HASH_LENGTH + 20];
@@ -327,12 +326,8 @@ int encrypt_text(const char* pad_identifier, const char* input_text) {
} }
} }
// Calculate XOR checksum of pad file // Use pad_chksum directly - it's already the checksum from the filename
if (calculate_checksum(pad_path, chksum_hex) != 0) { // No need to recalculate by reading the entire pad file
printf("Error: Cannot calculate pad checksum\n");
free(pad_chksum);
return 1;
}
// Get input text - either from parameter or user input // Get input text - either from parameter or user input
if (input_text != NULL) { if (input_text != NULL) {
@@ -406,18 +401,40 @@ int encrypt_text(const char* pad_identifier, const char* input_text) {
return 1; return 1;
} }
// Check if we have enough pad space // Calculate chunk size for padding (exponential bucketing)
struct stat pad_stat; size_t chunk_size = calculate_chunk_size(input_len);
if (stat(pad_path, &pad_stat) != 0) {
printf("Error: Cannot get pad file size\n"); // Allocate buffer for padded message
unsigned char* padded_buffer = malloc(chunk_size);
if (!padded_buffer) {
printf("Error: Memory allocation failed\n");
free(pad_chksum); free(pad_chksum);
return 1; return 1;
} }
if (current_offset + input_len > (uint64_t)pad_stat.st_size) { // Copy message to buffer and apply padding
memcpy(padded_buffer, text_buffer, input_len);
if (apply_padme_padding(padded_buffer, input_len, chunk_size) != 0) {
printf("Error: Failed to apply padding\n");
free(padded_buffer);
free(pad_chksum);
return 1;
}
// Check if we have enough pad space (now using chunk_size instead of input_len)
struct stat pad_stat;
if (stat(pad_path, &pad_stat) != 0) {
printf("Error: Cannot get pad file size\n");
free(padded_buffer);
free(pad_chksum);
return 1;
}
if (current_offset + chunk_size > (uint64_t)pad_stat.st_size) {
printf("Error: Not enough pad space remaining\n"); printf("Error: Not enough pad space remaining\n");
printf("Need: %lu bytes, Available: %lu bytes\n", printf("Need: %lu bytes, Available: %lu bytes\n",
input_len, (uint64_t)pad_stat.st_size - current_offset); chunk_size, (uint64_t)pad_stat.st_size - current_offset);
free(padded_buffer);
free(pad_chksum); free(pad_chksum);
return 1; return 1;
} }
@@ -437,37 +454,40 @@ int encrypt_text(const char* pad_identifier, const char* input_text) {
return 1; return 1;
} }
unsigned char* pad_data = malloc(input_len); unsigned char* pad_data = malloc(chunk_size);
if (fread(pad_data, 1, input_len, pad_file) != input_len) { if (fread(pad_data, 1, chunk_size, pad_file) != chunk_size) {
printf("Error: Cannot read pad data\n"); printf("Error: Cannot read pad data\n");
free(pad_data); free(pad_data);
fclose(pad_file); fclose(pad_file);
free(padded_buffer);
free(pad_chksum); free(pad_chksum);
return 1; return 1;
} }
fclose(pad_file); fclose(pad_file);
// Use universal XOR operation for encryption // Use universal XOR operation for encryption (now with padded data)
unsigned char* ciphertext = malloc(input_len); unsigned char* ciphertext = malloc(chunk_size);
if (universal_xor_operation((const unsigned char*)text_buffer, input_len, pad_data, ciphertext) != 0) { if (universal_xor_operation(padded_buffer, chunk_size, pad_data, ciphertext) != 0) {
printf("Error: Encryption operation failed\n"); printf("Error: Encryption operation failed\n");
free(pad_data); free(pad_data);
free(ciphertext); free(ciphertext);
free(padded_buffer);
free(pad_chksum); free(pad_chksum);
return 1; return 1;
} }
// Update state offset // Update state offset (now using chunk_size)
if (write_state_offset(pad_chksum, current_offset + input_len) != 0) { if (write_state_offset(pad_chksum, current_offset + chunk_size) != 0) {
printf("Warning: Failed to update state file\n"); printf("Warning: Failed to update state file\n");
} }
// Use universal ASCII armor generator // Use universal ASCII armor generator (now with chunk_size)
char* ascii_output; char* ascii_output;
if (generate_ascii_armor(chksum_hex, current_offset, ciphertext, input_len, &ascii_output) != 0) { if (generate_ascii_armor(pad_chksum, current_offset, ciphertext, chunk_size, &ascii_output) != 0) {
printf("Error: Failed to generate ASCII armor\n"); printf("Error: Failed to generate ASCII armor\n");
free(pad_data); free(pad_data);
free(ciphertext); free(ciphertext);
free(padded_buffer);
free(pad_chksum); free(pad_chksum);
return 1; return 1;
} }
@@ -484,6 +504,7 @@ int encrypt_text(const char* pad_identifier, const char* input_text) {
// Cleanup // Cleanup
free(pad_data); free(pad_data);
free(ciphertext); free(ciphertext);
free(padded_buffer);
free(ascii_output); free(ascii_output);
free(pad_chksum); free(pad_chksum);
@@ -592,36 +613,14 @@ int universal_decrypt(const char* input_data, const char* output_target, decrypt
return 1; return 1;
} }
// Validate pad integrity // Pad integrity validation disabled for performance
int integrity_result = validate_pad_integrity(pad_path, stored_chksum); // The checksum is already verified by matching the filename
if (integrity_result == 3) { // If you need to verify pad integrity, the pad file would need to be read entirely
if (mode == DECRYPT_MODE_SILENT) { // which is very slow for large pads (multi-GB files)
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]; // Skip integrity check - trust the filename checksum
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) { if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Pad integrity: VERIFIED\n"); printf("Using pad: %s\n", stored_chksum);
}
} }
// Decode base64 ciphertext // Decode base64 ciphertext
@@ -659,6 +658,22 @@ int universal_decrypt(const char* input_data, const char* output_target, decrypt
return 1; return 1;
} }
// Remove padding to get actual message
size_t actual_msg_len;
if (remove_padme_padding(ciphertext, ciphertext_len, &actual_msg_len) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Invalid padding - message may be corrupted\n");
} else {
printf("Error: Invalid padding - message may be corrupted\n");
}
free(ciphertext);
free(pad_data);
return 1;
}
// Update ciphertext_len to actual message length
ciphertext_len = actual_msg_len;
// Output based on mode // Output based on mode
if (mode == DECRYPT_MODE_FILE_TO_FILE) { if (mode == DECRYPT_MODE_FILE_TO_FILE) {
// Write to output file // Write to output file
@@ -746,7 +761,6 @@ int encrypt_file(const char* pad_identifier, const char* input_file, const char*
return 1; return 1;
} }
char chksum_hex[MAX_HASH_LENGTH];
uint64_t current_offset; uint64_t current_offset;
char pad_path[MAX_HASH_LENGTH + 20]; char pad_path[MAX_HASH_LENGTH + 20];
@@ -768,6 +782,9 @@ int encrypt_file(const char* pad_identifier, const char* input_file, const char*
return 1; return 1;
} }
// Calculate chunk size for padding (exponential bucketing)
size_t chunk_size = calculate_chunk_size(file_size);
// Check if pad file exists // Check if pad file exists
if (access(pad_path, R_OK) != 0) { if (access(pad_path, R_OK) != 0) {
printf("Error: Pad file %s not found\n", pad_path); printf("Error: Pad file %s not found\n", pad_path);
@@ -791,12 +808,8 @@ int encrypt_file(const char* pad_identifier, const char* input_file, const char*
} }
} }
// Calculate XOR checksum of pad file // Use pad_chksum directly - it's already the checksum from the filename
if (calculate_checksum(pad_path, chksum_hex) != 0) { // No need to recalculate by reading the entire pad file
printf("Error: Cannot calculate pad checksum\n");
free(pad_chksum);
return 1;
}
// Check if we have enough pad space // Check if we have enough pad space
struct stat pad_stat; struct stat pad_stat;
@@ -806,10 +819,10 @@ int encrypt_file(const char* pad_identifier, const char* input_file, const char*
return 1; return 1;
} }
if (current_offset + file_size > (uint64_t)pad_stat.st_size) { if (current_offset + chunk_size > (uint64_t)pad_stat.st_size) {
printf("Error: Not enough pad space remaining\n"); printf("Error: Not enough pad space remaining\n");
printf("Need: %lu bytes, Available: %lu bytes\n", printf("Need: %lu bytes (file: %lu + padding), Available: %lu bytes\n",
file_size, (uint64_t)pad_stat.st_size - current_offset); chunk_size, file_size, (uint64_t)pad_stat.st_size - current_offset);
free(pad_chksum); free(pad_chksum);
return 1; return 1;
} }
@@ -854,65 +867,92 @@ int encrypt_file(const char* pad_identifier, const char* input_file, const char*
return 1; return 1;
} }
// Read and encrypt file // Allocate buffer for padded file data
unsigned char buffer[64 * 1024]; unsigned char* file_data = malloc(file_size);
unsigned char pad_buffer[64 * 1024]; if (!file_data) {
unsigned char* encrypted_data = malloc(file_size); printf("Error: Memory allocation failed\n");
uint64_t bytes_processed = 0; fclose(input_fp);
fclose(pad_file);
free(pad_chksum);
return 1;
}
printf("Encrypting %s...\n", input_file); printf("Encrypting %s...\n", input_file);
while (bytes_processed < file_size) { // Read entire file
uint64_t chunk_size = sizeof(buffer); if (fread(file_data, 1, file_size, input_fp) != file_size) {
if (file_size - bytes_processed < chunk_size) { printf("Error: Cannot read input file\n");
chunk_size = file_size - bytes_processed; free(file_data);
fclose(input_fp);
fclose(pad_file);
free(pad_chksum);
return 1;
}
fclose(input_fp);
// Allocate buffer for padded data
unsigned char* padded_data = malloc(chunk_size);
if (!padded_data) {
printf("Error: Memory allocation failed\n");
free(file_data);
fclose(pad_file);
free(pad_chksum);
return 1;
} }
// Read file data // Copy file data and apply padding
if (fread(buffer, 1, chunk_size, input_fp) != chunk_size) { memcpy(padded_data, file_data, file_size);
printf("Error: Cannot read input file data\n"); free(file_data);
free(encrypted_data);
fclose(input_fp); if (apply_padme_padding(padded_data, file_size, chunk_size) != 0) {
printf("Error: Failed to apply padding\n");
free(padded_data);
fclose(pad_file); fclose(pad_file);
free(pad_chksum); free(pad_chksum);
return 1; return 1;
} }
// Read pad data // Read pad data
if (fread(pad_buffer, 1, chunk_size, pad_file) != chunk_size) { unsigned char* pad_data = malloc(chunk_size);
if (!pad_data) {
printf("Error: Memory allocation failed\n");
free(padded_data);
fclose(pad_file);
free(pad_chksum);
return 1;
}
if (fread(pad_data, 1, chunk_size, pad_file) != chunk_size) {
printf("Error: Cannot read pad data\n"); printf("Error: Cannot read pad data\n");
free(encrypted_data); free(padded_data);
fclose(input_fp); free(pad_data);
fclose(pad_file); fclose(pad_file);
free(pad_chksum); free(pad_chksum);
return 1; return 1;
} }
fclose(pad_file);
// Use universal XOR operation for encryption // Encrypt padded data
if (universal_xor_operation(buffer, chunk_size, pad_buffer, &encrypted_data[bytes_processed]) != 0) { unsigned char* encrypted_data = malloc(chunk_size);
if (!encrypted_data) {
printf("Error: Memory allocation failed\n");
free(padded_data);
free(pad_data);
free(pad_chksum);
return 1;
}
if (universal_xor_operation(padded_data, chunk_size, pad_data, encrypted_data) != 0) {
printf("Error: Encryption operation failed\n"); printf("Error: Encryption operation failed\n");
free(padded_data);
free(pad_data);
free(encrypted_data); free(encrypted_data);
fclose(input_fp);
fclose(pad_file);
free(pad_chksum); free(pad_chksum);
return 1; return 1;
} }
bytes_processed += chunk_size; free(padded_data);
free(pad_data);
// Show progress for large files (> 10MB)
if (file_size > 10 * 1024 * 1024 && bytes_processed % (1024 * 1024) == 0) {
// show_progress(bytes_processed, file_size, start_time); // MOVED TO src/util.c
}
}
if (file_size > 10 * 1024 * 1024) {
// show_progress(file_size, file_size, start_time); // MOVED TO src/util.c
printf("\n");
}
fclose(input_fp);
fclose(pad_file);
// Write output file // Write output file
if (ascii_armor) { if (ascii_armor) {
@@ -925,9 +965,9 @@ int encrypt_file(const char* pad_identifier, const char* input_file, const char*
return 1; return 1;
} }
// Use universal ASCII armor generator // Use universal ASCII armor generator (now with chunk_size)
char* ascii_output; char* ascii_output;
if (generate_ascii_armor(chksum_hex, current_offset, encrypted_data, file_size, &ascii_output) != 0) { if (generate_ascii_armor(pad_chksum, current_offset, encrypted_data, chunk_size, &ascii_output) != 0) {
printf("Error: Failed to generate ASCII armor\n"); printf("Error: Failed to generate ASCII armor\n");
fclose(output_fp); fclose(output_fp);
free(encrypted_data); free(encrypted_data);
@@ -961,7 +1001,7 @@ int encrypt_file(const char* pad_identifier, const char* input_file, const char*
// Pad checksum: 32 bytes (binary) // Pad checksum: 32 bytes (binary)
unsigned char pad_chksum_bin[32]; unsigned char pad_chksum_bin[32];
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
sscanf(chksum_hex + i*2, "%2hhx", &pad_chksum_bin[i]); sscanf(pad_chksum + i*2, "%2hhx", &pad_chksum_bin[i]);
} }
fwrite(pad_chksum_bin, 1, 32, output_fp); fwrite(pad_chksum_bin, 1, 32, output_fp);
@@ -973,17 +1013,17 @@ int encrypt_file(const char* pad_identifier, const char* input_file, const char*
uint32_t file_mode = input_stat.st_mode; uint32_t file_mode = input_stat.st_mode;
fwrite(&file_mode, sizeof(uint32_t), 1, output_fp); fwrite(&file_mode, sizeof(uint32_t), 1, output_fp);
// File size: 8 bytes // File size: 8 bytes (original file size, not padded)
fwrite(&file_size, sizeof(uint64_t), 1, output_fp); fwrite(&file_size, sizeof(uint64_t), 1, output_fp);
// Encrypted data // Encrypted data (padded)
fwrite(encrypted_data, 1, file_size, output_fp); fwrite(encrypted_data, 1, chunk_size, output_fp);
fclose(output_fp); fclose(output_fp);
} }
// Update state offset // Update state offset (now using chunk_size)
if (write_state_offset(pad_chksum, current_offset + file_size) != 0) { if (write_state_offset(pad_chksum, current_offset + chunk_size) != 0) {
printf("Warning: Failed to update state file\n"); printf("Warning: Failed to update state file\n");
} }
@@ -1044,14 +1084,14 @@ int decrypt_binary_file(FILE* input_fp, const char* output_file) {
unsigned char pad_chksum_bin[32]; unsigned char pad_chksum_bin[32];
uint64_t pad_offset; uint64_t pad_offset;
uint32_t file_mode; uint32_t file_mode;
uint64_t file_size; uint64_t original_file_size;
if (fread(magic, 1, 4, input_fp) != 4 || if (fread(magic, 1, 4, input_fp) != 4 ||
fread(&version, sizeof(uint16_t), 1, input_fp) != 1 || fread(&version, sizeof(uint16_t), 1, input_fp) != 1 ||
fread(pad_chksum_bin, 1, 32, input_fp) != 32 || fread(pad_chksum_bin, 1, 32, input_fp) != 32 ||
fread(&pad_offset, sizeof(uint64_t), 1, input_fp) != 1 || fread(&pad_offset, sizeof(uint64_t), 1, input_fp) != 1 ||
fread(&file_mode, sizeof(uint32_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) { fread(&original_file_size, sizeof(uint64_t), 1, input_fp) != 1) {
printf("Error: Cannot read binary header\n"); printf("Error: Cannot read binary header\n");
fclose(input_fp); fclose(input_fp);
return 1; return 1;
@@ -1071,7 +1111,7 @@ int decrypt_binary_file(FILE* input_fp, const char* output_file) {
pad_chksum_hex[64] = '\0'; pad_chksum_hex[64] = '\0';
printf("Decrypting binary file...\n"); printf("Decrypting binary file...\n");
printf("File size: %lu bytes\n", file_size); printf("Original file size: %lu bytes\n", original_file_size);
// Check if we have the required pad // Check if we have the required pad
char pad_path[MAX_HASH_LENGTH + 20]; char pad_path[MAX_HASH_LENGTH + 20];
@@ -1096,9 +1136,18 @@ int decrypt_binary_file(FILE* input_fp, const char* output_file) {
output_file = default_output; output_file = default_output;
} }
// Read encrypted data // Calculate chunk size (encrypted data is padded)
unsigned char* encrypted_data = malloc(file_size); size_t chunk_size = calculate_chunk_size(original_file_size);
if (fread(encrypted_data, 1, file_size, input_fp) != file_size) {
// Read encrypted (padded) data
unsigned char* encrypted_data = malloc(chunk_size);
if (!encrypted_data) {
printf("Error: Memory allocation failed\n");
fclose(input_fp);
return 1;
}
if (fread(encrypted_data, 1, chunk_size, input_fp) != chunk_size) {
printf("Error: Cannot read encrypted data\n"); printf("Error: Cannot read encrypted data\n");
free(encrypted_data); free(encrypted_data);
fclose(input_fp); fclose(input_fp);
@@ -1121,8 +1170,15 @@ int decrypt_binary_file(FILE* input_fp, const char* output_file) {
return 1; return 1;
} }
unsigned char* pad_data = malloc(file_size); unsigned char* pad_data = malloc(chunk_size);
if (fread(pad_data, 1, file_size, pad_file) != file_size) { if (!pad_data) {
printf("Error: Memory allocation failed\n");
free(encrypted_data);
fclose(pad_file);
return 1;
}
if (fread(pad_data, 1, chunk_size, pad_file) != chunk_size) {
printf("Error: Cannot read pad data\n"); printf("Error: Cannot read pad data\n");
free(encrypted_data); free(encrypted_data);
free(pad_data); free(pad_data);
@@ -1132,26 +1188,40 @@ int decrypt_binary_file(FILE* input_fp, const char* output_file) {
fclose(pad_file); fclose(pad_file);
// Use universal XOR operation for decryption // Use universal XOR operation for decryption
if (universal_xor_operation(encrypted_data, file_size, pad_data, encrypted_data) != 0) { if (universal_xor_operation(encrypted_data, chunk_size, pad_data, encrypted_data) != 0) {
printf("Error: Decryption operation failed\n"); printf("Error: Decryption operation failed\n");
free(encrypted_data); free(encrypted_data);
free(pad_data); free(pad_data);
return 1; return 1;
} }
// Write decrypted file free(pad_data);
// Remove padding to get original file
size_t actual_file_size;
if (remove_padme_padding(encrypted_data, chunk_size, &actual_file_size) != 0) {
printf("Error: Invalid padding - file may be corrupted\n");
free(encrypted_data);
return 1;
}
// Verify the actual size matches the stored original size
if (actual_file_size != original_file_size) {
printf("Warning: Decrypted size (%lu) doesn't match stored size (%lu)\n",
actual_file_size, original_file_size);
}
// Write decrypted file (using actual_file_size)
FILE* output_fp = fopen(output_file, "wb"); FILE* output_fp = fopen(output_file, "wb");
if (!output_fp) { if (!output_fp) {
printf("Error: Cannot create output file %s\n", output_file); printf("Error: Cannot create output file %s\n", output_file);
free(encrypted_data); free(encrypted_data);
free(pad_data);
return 1; return 1;
} }
if (fwrite(encrypted_data, 1, file_size, output_fp) != file_size) { if (fwrite(encrypted_data, 1, actual_file_size, output_fp) != actual_file_size) {
printf("Error: Cannot write decrypted data\n"); printf("Error: Cannot write decrypted data\n");
free(encrypted_data); free(encrypted_data);
free(pad_data);
fclose(output_fp); fclose(output_fp);
return 1; return 1;
} }
@@ -1165,12 +1235,14 @@ int decrypt_binary_file(FILE* input_fp, const char* output_file) {
printf("File decrypted successfully: %s\n", output_file); printf("File decrypted successfully: %s\n", output_file);
printf("Restored permissions and metadata\n"); printf("Restored permissions and metadata\n");
// Only pause if output is not a temporary file (directory decryption uses /tmp/)
if (strncmp(output_file, "/tmp/", 5) != 0) {
// Pause before returning to menu to let user see the success message // Pause before returning to menu to let user see the success message
print_centered_header("File Decryption Complete", 1); print_centered_header("File Decryption Complete", 1);
}
// Cleanup // Cleanup
free(encrypted_data); free(encrypted_data);
free(pad_data);
return 0; return 0;
} }

View File

@@ -219,9 +219,23 @@ int add_entropy_chacha20(const char* pad_chksum, const unsigned char* entropy_da
unsigned char buffer[64 * 1024]; // 64KB chunks unsigned char buffer[64 * 1024]; // 64KB chunks
unsigned char keystream[64 * 1024]; unsigned char keystream[64 * 1024];
uint64_t offset = 0; uint64_t offset = 0;
uint32_t counter = 0; uint32_t counter_low = 0;
uint32_t counter_high = 0;
time_t start_time = time(NULL); time_t start_time = time(NULL);
// Use extended counter for pads larger than 256GB
// 256GB = 2^32 blocks * 64 bytes = 274,877,906,944 bytes
int use_extended = (pad_size > 274877906944ULL);
// For extended mode, use reduced 8-byte nonce
unsigned char nonce_reduced[8];
if (use_extended) {
memcpy(nonce_reduced, nonce + 4, 8);
if (display_progress) {
printf("Using extended counter mode for large pad (>256GB)\n");
}
}
while (offset < pad_size) { while (offset < pad_size) {
size_t chunk_size = sizeof(buffer); size_t chunk_size = sizeof(buffer);
if (pad_size - offset < chunk_size) { if (pad_size - offset < chunk_size) {
@@ -237,7 +251,15 @@ int add_entropy_chacha20(const char* pad_chksum, const unsigned char* entropy_da
} }
// Generate keystream for this chunk // Generate keystream for this chunk
if (chacha20_encrypt(key, counter, nonce, buffer, keystream, chunk_size) != 0) { int chacha_result;
if (use_extended) {
chacha_result = chacha20_encrypt_extended(key, counter_low, counter_high,
nonce_reduced, buffer, keystream, chunk_size);
} else {
chacha_result = chacha20_encrypt(key, counter_low, nonce, buffer, keystream, chunk_size);
}
if (chacha_result != 0) {
printf("Error: Chacha20 keystream generation failed\n"); printf("Error: Chacha20 keystream generation failed\n");
fclose(pad_file); fclose(pad_file);
chmod(pad_path, S_IRUSR); chmod(pad_path, S_IRUSR);
@@ -265,7 +287,16 @@ int add_entropy_chacha20(const char* pad_chksum, const unsigned char* entropy_da
} }
offset += chunk_size; offset += chunk_size;
counter += (chunk_size + 63) / 64; // Round up for block count
// Update counters
uint32_t blocks = (chunk_size + 63) / 64; // Round up for block count
uint32_t old_counter_low = counter_low;
counter_low += blocks;
// Check for overflow and increment high counter
if (counter_low < old_counter_low) {
counter_high++;
}
// Show progress for large pads // Show progress for large pads
if (display_progress && offset % (64 * 1024 * 1024) == 0) { // Every 64MB if (display_progress && offset % (64 * 1024 * 1024) == 0) { // Every 64MB
@@ -282,7 +313,8 @@ int add_entropy_chacha20(const char* pad_chksum, const unsigned char* entropy_da
if (display_progress) { if (display_progress) {
show_progress(pad_size, pad_size, start_time); show_progress(pad_size, pad_size, start_time);
printf("\n✓ Entropy successfully added to pad using Chacha20\n"); printf("\n✓ Entropy successfully added to pad using Chacha20%s\n",
use_extended ? " (extended counter)" : "");
printf("✓ Pad integrity maintained\n"); printf("✓ Pad integrity maintained\n");
printf("✓ %zu bytes of entropy distributed across entire pad\n", entropy_size); printf("✓ %zu bytes of entropy distributed across entire pad\n", entropy_size);
printf("✓ Pad restored to read-only mode\n"); printf("✓ Pad restored to read-only mode\n");

View File

@@ -23,7 +23,7 @@
#include <ctype.h> #include <ctype.h>
// Version - Updated automatically by build.sh // Version - Updated automatically by build.sh
#define OTP_VERSION "v0.3.34" #define OTP_VERSION "v0.3.49"
// Constants // Constants
#define MAX_INPUT_SIZE 4096 #define MAX_INPUT_SIZE 4096
@@ -130,6 +130,7 @@ char* get_preferred_editor(void);
char* get_preferred_file_manager(void); char* get_preferred_file_manager(void);
int launch_text_editor(const char* initial_content, char* result_buffer, size_t buffer_size); int launch_text_editor(const char* initial_content, char* result_buffer, size_t buffer_size);
int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size); int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size);
int launch_directory_manager(const char* start_directory, char* selected_dir, size_t buffer_size);
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// CORE CRYPTOGRAPHIC OPERATIONS // CORE CRYPTOGRAPHIC OPERATIONS
@@ -238,6 +239,23 @@ int update_pad_checksum_after_entropy(const char* old_chksum, char* new_chksum);
int rename_pad_files_safely(const char* old_chksum, const char* new_chksum); int rename_pad_files_safely(const char* old_chksum, const char* new_chksum);
int is_pad_unused(const char* pad_chksum); int is_pad_unused(const char* pad_chksum);
////////////////////////////////////////////////////////////////////////////////
// DIRECTORY ARCHIVING AND COMPRESSION FUNCTIONS
////////////////////////////////////////////////////////////////////////////////
// Directory encryption/decryption (TAR + GZIP + OTP)
int encrypt_directory(const char* dir_path, const char* pad_identifier, const char* output_file);
int decrypt_and_extract_directory(const char* encrypted_file, const char* output_dir);
int is_compressed_tar_archive(const char* file_path);
// TAR archive operations
int create_tar_archive(const char* dir_path, const char* tar_output_path);
int extract_tar_archive(const char* tar_path, const char* output_dir);
// Compression operations
int compress_file_gzip(const char* input_path, const char* output_path);
int decompress_file_gzip(const char* input_path, const char* output_path);
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// DIRECTORY MANAGEMENT FUNCTIONS // DIRECTORY MANAGEMENT FUNCTIONS
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -249,6 +267,16 @@ const char* get_files_directory(void);
void get_default_file_path(const char* filename, char* result_path, size_t result_size); void get_default_file_path(const char* filename, char* result_path, size_t result_size);
void get_directory_display(const char* file_path, char* result, size_t result_size); void get_directory_display(const char* file_path, char* result, size_t result_size);
////////////////////////////////////////////////////////////////////////////////
// MESSAGE PADDING FUNCTIONS
////////////////////////////////////////////////////////////////////////////////
// Exponential bucketing and ISO/IEC 9797-1 Method 2 (Padmé) padding
size_t calculate_chunk_size(size_t msg_len);
int apply_padme_padding(unsigned char* buffer, size_t msg_len, size_t chunk_size);
int remove_padme_padding(const unsigned char* buffer, size_t chunk_size, size_t* msg_len);
void format_chunk_size(size_t chunk_size, char* buffer, size_t buffer_size);
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// UTILITY FUNCTIONS // UTILITY FUNCTIONS
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -259,6 +287,7 @@ char* find_pad_by_prefix(const char* prefix);
int show_pad_info(const char* chksum); int show_pad_info(const char* chksum);
void show_progress(uint64_t current, uint64_t total, time_t start_time); void show_progress(uint64_t current, uint64_t total, time_t start_time);
void format_time_remaining(double seconds, char* buffer, size_t buffer_size); void format_time_remaining(double seconds, char* buffer, size_t buffer_size);
int is_escape_input(const char* input);
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// FILE OPERATIONS // FILE OPERATIONS
@@ -314,6 +343,7 @@ int handle_decrypt_menu(void);
int handle_pads_menu(void); int handle_pads_menu(void);
int handle_text_encrypt(void); int handle_text_encrypt(void);
int handle_file_encrypt(void); int handle_file_encrypt(void);
int handle_directory_encrypt(void);
int handle_verify_pad(const char* pad_chksum); int handle_verify_pad(const char* pad_chksum);
int handle_delete_pad(const char* pad_chksum); int handle_delete_pad(const char* pad_chksum);

View File

@@ -161,3 +161,45 @@ int chacha20_encrypt(const uint8_t key[32], uint32_t counter,
return 0; return 0;
} }
int chacha20_encrypt_extended(const uint8_t key[32], uint32_t counter_low,
uint32_t counter_high, const uint8_t nonce[8],
const uint8_t* input, uint8_t* output, size_t length) {
uint8_t keystream[CHACHA20_BLOCK_SIZE];
uint8_t extended_nonce[12];
size_t offset = 0;
while (length > 0) {
/* Build extended 12-byte nonce: [counter_high (4 bytes)][nonce (8 bytes)] */
u32_to_bytes_le(counter_high, extended_nonce);
memcpy(extended_nonce + 4, nonce, 8);
/* Generate keystream block using extended nonce */
int ret = chacha20_block(key, counter_low, extended_nonce, keystream);
if (ret != 0) {
return ret;
}
/* XOR with input to produce output */
size_t block_len = (length < CHACHA20_BLOCK_SIZE) ? length : CHACHA20_BLOCK_SIZE;
for (size_t i = 0; i < block_len; i++) {
output[offset + i] = input[offset + i] ^ keystream[i];
}
/* Move to next block */
offset += block_len;
length -= block_len;
counter_low++;
/* Check for counter_low overflow and increment counter_high */
if (counter_low == 0) {
counter_high++;
/* Check for counter_high overflow (extremely unlikely - > 1 exabyte) */
if (counter_high == 0) {
return -1; /* Extended counter wrapped around */
}
}
}
return 0;
}

View File

@@ -79,6 +79,25 @@ int chacha20_encrypt(const uint8_t key[32], uint32_t counter,
const uint8_t nonce[12], const uint8_t* input, const uint8_t nonce[12], const uint8_t* input,
uint8_t* output, size_t length); uint8_t* output, size_t length);
/**
* ChaCha20 encryption/decryption with extended counter (64-bit)
*
* Extended version that supports files larger than 256GB by using
* part of the nonce as a high-order counter extension.
*
* @param key[in] 32-byte key
* @param counter_low[in] Initial 32-bit counter value (low bits)
* @param counter_high[in] Initial 32-bit counter value (high bits)
* @param nonce[in] 8-byte reduced nonce (instead of 12)
* @param input[in] Input data to encrypt/decrypt
* @param output[out] Output buffer (can be same as input)
* @param length[in] Length of input data in bytes
* @return 0 on success, negative on error
*/
int chacha20_encrypt_extended(const uint8_t key[32], uint32_t counter_low,
uint32_t counter_high, const uint8_t nonce[8],
const uint8_t* input, uint8_t* output, size_t length);
/* /*
* ============================================================================ * ============================================================================
* UTILITY FUNCTIONS * UTILITY FUNCTIONS

92
src/padding.c Normal file
View File

@@ -0,0 +1,92 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "main.h"
////////////////////////////////////////////////////////////////////////////////
// MESSAGE PADDING IMPLEMENTATION
// ISO/IEC 9797-1 Method 2 (Padmé Padding)
// with Exponential Bucketing for Traffic Analysis Resistance
////////////////////////////////////////////////////////////////////////////////
// Calculate required chunk size using exponential bucketing
// Starting at 256 bytes, doubling each time
// Provides strong protection against length analysis attacks
size_t calculate_chunk_size(size_t msg_len) {
size_t chunk = 256; // Minimum chunk size: 256 bytes
// Need space for message + 0x80 byte (minimum 1 byte padding)
while (chunk < msg_len + 1) {
chunk *= 2;
}
return chunk;
}
// Apply ISO/IEC 9797-1 Method 2 (Padmé) padding
// Appends 0x80 byte followed by 0x00 bytes to reach chunk boundary
// Returns 0 on success, non-zero on error
int apply_padme_padding(unsigned char* buffer, size_t msg_len, size_t chunk_size) {
if (!buffer) {
return 1; // Error: null pointer
}
if (chunk_size < msg_len + 1) {
return 2; // Error: chunk size too small for message + padding
}
// Apply padding: 0x80 followed by 0x00 bytes
buffer[msg_len] = 0x80;
// Fill remaining bytes with 0x00
if (chunk_size > msg_len + 1) {
memset(buffer + msg_len + 1, 0x00, chunk_size - msg_len - 1);
}
return 0; // Success
}
// Remove ISO/IEC 9797-1 Method 2 (Padmé) padding
// Scans backwards for 0x80 marker, validates padding
// Returns 0 on success, non-zero on error
// Sets msg_len to the actual message length (excluding padding)
int remove_padme_padding(const unsigned char* buffer, size_t chunk_size, size_t* msg_len) {
if (!buffer || !msg_len) {
return 1; // Error: null pointer
}
if (chunk_size == 0) {
return 2; // Error: invalid chunk size
}
// Scan backwards from end to find 0x80 marker
for (int i = chunk_size - 1; i >= 0; i--) {
if (buffer[i] == 0x80) {
// Found the padding marker
*msg_len = i;
return 0; // Success
} else if (buffer[i] != 0x00) {
// Found non-zero, non-0x80 byte - invalid padding
return 3; // Error: invalid padding (corrupted or tampered data)
}
}
// No 0x80 marker found - invalid padding
return 4; // Error: no padding marker found
}
// Helper function to get human-readable chunk size
// Useful for debugging and user feedback
void format_chunk_size(size_t chunk_size, char* buffer, size_t buffer_size) {
if (!buffer || buffer_size == 0) return;
if (chunk_size < 1024) {
snprintf(buffer, buffer_size, "%zu bytes", chunk_size);
} else if (chunk_size < 1024 * 1024) {
snprintf(buffer, buffer_size, "%.1f KB", chunk_size / 1024.0);
} else if (chunk_size < 1024 * 1024 * 1024) {
snprintf(buffer, buffer_size, "%.1f MB", chunk_size / (1024.0 * 1024.0));
} else {
snprintf(buffer, buffer_size, "%.1f GB", chunk_size / (1024.0 * 1024.0 * 1024.0));
}
}

View File

@@ -253,10 +253,10 @@ int generate_pad(uint64_t size_bytes, int display_progress) {
} }
// Initialize state file with offset 32 (first 32 bytes reserved for checksum encryption) // Initialize state file with offset 32 (first 32 bytes reserved for checksum encryption)
FILE* state_file = fopen(state_path, "wb"); FILE* state_file = fopen(state_path, "w");
if (state_file) { if (state_file) {
uint64_t reserved_bytes = 32; uint64_t reserved_bytes = 32;
fwrite(&reserved_bytes, sizeof(uint64_t), 1, state_file); fprintf(state_file, "offset=%lu\n", reserved_bytes);
fclose(state_file); fclose(state_file);
} else { } else {
printf("Error: Failed to create state file\n"); printf("Error: Failed to create state file\n");
@@ -292,7 +292,7 @@ int read_state_offset(const char* pad_chksum, uint64_t* offset) {
return 0; return 0;
} }
// Try to read as text format first (new format) // Read text format only (required format: "offset=<number>")
char line[128]; char line[128];
if (fgets(line, sizeof(line), state_file)) { if (fgets(line, sizeof(line), state_file)) {
// Check if it's text format (starts with "offset=") // Check if it's text format (starts with "offset=")
@@ -302,21 +302,13 @@ int read_state_offset(const char* pad_chksum, uint64_t* offset) {
return 0; return 0;
} }
// Not text format, try binary format (legacy) // Not in proper text format - error
fclose(state_file); fclose(state_file);
state_file = fopen(state_filename, "rb"); fprintf(stderr, "Error: State file '%s' is not in proper text format\n", state_filename);
if (!state_file) { fprintf(stderr, "Expected format: offset=<number>\n");
fprintf(stderr, "Please convert old binary state files to text format\n");
*offset = 0; *offset = 0;
return 0; return 1;
}
if (fread(offset, sizeof(uint64_t), 1, state_file) != 1) {
fclose(state_file);
*offset = 0;
return 0;
}
fclose(state_file);
return 0;
} }
fclose(state_file); fclose(state_file);
@@ -478,6 +470,13 @@ char* select_pad_interactive(const char* title, const char* prompt, pad_filter_t
return NULL; return NULL;
} }
// If only one pad available, auto-select it
if (pad_count == 1) {
printf("\n%s\n", title);
printf("Only one pad available - auto-selecting: %.16s...\n\n", pads[0].chksum);
return strdup(pads[0].chksum);
}
// Calculate minimal unique prefixes for each pad // Calculate minimal unique prefixes for each pad
char prefixes[100][65]; char prefixes[100][65];
int prefix_lengths[100]; int prefix_lengths[100];

View File

@@ -1,10 +1,12 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include "main.h" #include "main.h"
// Global state variables // Global state variables
static char current_pads_dir[512] = DEFAULT_PADS_DIR; static char current_pads_dir[512] = "";
static int is_interactive_mode = 0; static int is_interactive_mode = 0;
static int pads_dir_initialized = 0;
// Terminal dimensions (moved from ui.c to state.c for global access) // Terminal dimensions (moved from ui.c to state.c for global access)
static int terminal_width = 80; // Default fallback width static int terminal_width = 80; // Default fallback width
@@ -13,6 +15,18 @@ static int terminal_height = 24; // Default fallback height
// Getters and setters for global state // Getters and setters for global state
const char* get_current_pads_dir(void) { const char* get_current_pads_dir(void) {
// Initialize pads directory on first access if not already set
if (!pads_dir_initialized && strlen(current_pads_dir) == 0) {
char* home_dir = getenv("HOME");
if (home_dir) {
snprintf(current_pads_dir, sizeof(current_pads_dir), "%s/.otp/pads", home_dir);
} else {
// Fallback to relative path if HOME is not set
strncpy(current_pads_dir, DEFAULT_PADS_DIR, sizeof(current_pads_dir) - 1);
}
current_pads_dir[sizeof(current_pads_dir) - 1] = '\0';
pads_dir_initialized = 1;
}
return current_pads_dir; return current_pads_dir;
} }
@@ -20,6 +34,7 @@ void set_current_pads_dir(const char* dir) {
if (dir) { if (dir) {
strncpy(current_pads_dir, dir, sizeof(current_pads_dir) - 1); strncpy(current_pads_dir, dir, sizeof(current_pads_dir) - 1);
current_pads_dir[sizeof(current_pads_dir) - 1] = '\0'; current_pads_dir[sizeof(current_pads_dir) - 1] = '\0';
pads_dir_initialized = 1;
} }
} }

512
src/ui.c
View File

@@ -99,6 +99,9 @@ int interactive_mode(void) {
case 'F': case 'F':
handle_file_encrypt(); handle_file_encrypt();
break; break;
case 'R':
handle_directory_encrypt();
break;
case 'D': case 'D':
handle_decrypt_menu(); handle_decrypt_menu();
break; break;
@@ -127,6 +130,7 @@ void show_main_menu(void) {
printf(" \033[4mT\033[0mext encrypt\n"); //TEXT ENCRYPT printf(" \033[4mT\033[0mext encrypt\n"); //TEXT ENCRYPT
printf(" \033[4mF\033[0mile encrypt\n"); //FILE ENCRYPT printf(" \033[4mF\033[0mile encrypt\n"); //FILE ENCRYPT
printf(" Di\033[4mr\033[0mectory encrypt\n"); //DIRECTORY ENCRYPT
printf(" \033[4mD\033[0mecrypt\n"); //DECRYPT printf(" \033[4mD\033[0mecrypt\n"); //DECRYPT
printf(" \033[4mP\033[0mads\n"); //PADS printf(" \033[4mP\033[0mads\n"); //PADS
printf(" E\033[4mx\033[0mit\n"); //EXIT printf(" E\033[4mx\033[0mit\n"); //EXIT
@@ -180,7 +184,7 @@ int handle_encrypt_menu(void) {
printf("\nSelect encryption type:\n"); printf("\nSelect encryption type:\n");
printf(" 1. Text message\n"); printf(" 1. Text message\n");
printf(" 2. File\n"); printf(" 2. File\n");
printf("Enter choice (1-2): "); printf("Enter choice (1-2) or 'esc' to cancel: ");
char choice_input[10]; char choice_input[10];
if (!fgets(choice_input, sizeof(choice_input), stdin)) { if (!fgets(choice_input, sizeof(choice_input), stdin)) {
@@ -188,6 +192,14 @@ int handle_encrypt_menu(void) {
return 1; return 1;
} }
choice_input[strcspn(choice_input, "\n")] = 0;
// Check for ESC/cancel
if (is_escape_input(choice_input)) {
printf("Returning to main menu...\n");
return 0;
}
int choice = atoi(choice_input); int choice = atoi(choice_input);
if (choice == 1) { if (choice == 1) {
@@ -209,7 +221,7 @@ int handle_encrypt_menu(void) {
printf("\nFile selection options:\n"); printf("\nFile selection options:\n");
printf(" 1. Type file path directly\n"); printf(" 1. Type file path directly\n");
printf(" 2. Use file manager\n"); printf(" 2. Use file manager\n");
printf("Enter choice (1-2): "); printf("Enter choice (1-2) or 'esc' to cancel: ");
char file_choice[10]; char file_choice[10];
char input_file[512]; char input_file[512];
@@ -219,25 +231,45 @@ int handle_encrypt_menu(void) {
return 1; return 1;
} }
file_choice[strcspn(file_choice, "\n")] = 0;
// Check for ESC/cancel
if (is_escape_input(file_choice)) {
printf("Returning to main menu...\n");
return 0;
}
if (atoi(file_choice) == 2) { if (atoi(file_choice) == 2) {
// Use file manager // Use file manager
if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) { if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) {
printf("Falling back to manual file path entry.\n"); printf("Falling back to manual file path entry.\n");
printf("Enter input file path: "); printf("Enter input file path (or 'esc' to cancel): ");
if (!fgets(input_file, sizeof(input_file), stdin)) { if (!fgets(input_file, sizeof(input_file), stdin)) {
printf("Error: Failed to read input\n"); printf("Error: Failed to read input\n");
return 1; return 1;
} }
input_file[strcspn(input_file, "\n")] = 0; input_file[strcspn(input_file, "\n")] = 0;
// Check for ESC/cancel
if (is_escape_input(input_file)) {
printf("Returning to main menu...\n");
return 0;
}
} }
} else { } else {
// Direct file path input // Direct file path input
printf("Enter input file path: "); printf("Enter input file path (or 'esc' to cancel): ");
if (!fgets(input_file, sizeof(input_file), stdin)) { if (!fgets(input_file, sizeof(input_file), stdin)) {
printf("Error: Failed to read input\n"); printf("Error: Failed to read input\n");
return 1; return 1;
} }
input_file[strcspn(input_file, "\n")] = 0; input_file[strcspn(input_file, "\n")] = 0;
// Check for ESC/cancel
if (is_escape_input(input_file)) {
printf("Returning to main menu...\n");
return 0;
}
} }
// Check if file exists // Check if file exists
@@ -259,14 +291,24 @@ int handle_encrypt_menu(void) {
printf("\nSelect output format:\n"); printf("\nSelect output format:\n");
printf(" 1. Binary (.otp) - preserves file permissions\n"); printf(" 1. Binary (.otp) - preserves file permissions\n");
printf(" 2. ASCII (.otp.asc) - text-safe format\n"); printf(" 2. ASCII (.otp.asc) - text-safe format\n");
printf("Enter choice (1-2): "); printf("Enter choice (1-2) or 'esc' to cancel: ");
char format_input[10]; char format_input[10];
if (!fgets(format_input, sizeof(format_input), stdin)) { if (!fgets(format_input, sizeof(format_input), stdin)) {
printf("Error: Failed to read input\n"); printf("Error: Failed to read input\n");
free(selected_pad);
return 1; return 1;
} }
format_input[strcspn(format_input, "\n")] = 0;
// Check for ESC/cancel
if (is_escape_input(format_input)) {
printf("Returning to main menu...\n");
free(selected_pad);
return 0;
}
int ascii_armor = (atoi(format_input) == 2) ? 1 : 0; int ascii_armor = (atoi(format_input) == 2) ? 1 : 0;
// Generate default output filename with files directory and use enhanced input function // Generate default output filename with files directory and use enhanced input function
@@ -305,6 +347,7 @@ int handle_decrypt_menu(void) {
printf("\n"); printf("\n");
print_centered_header("Smart Decrypt", 0); print_centered_header("Smart Decrypt", 0);
printf("Enter encrypted data (paste ASCII armor), file path, or press Enter to browse files:\n"); printf("Enter encrypted data (paste ASCII armor), file path, or press Enter to browse files:\n");
printf("(Type 'esc' or 'q' to return to main menu)\n");
char input_line[MAX_LINE_LENGTH]; char input_line[MAX_LINE_LENGTH];
if (!fgets(input_line, sizeof(input_line), stdin)) { if (!fgets(input_line, sizeof(input_line), stdin)) {
@@ -315,46 +358,21 @@ int handle_decrypt_menu(void) {
// Remove newline // Remove newline
input_line[strcspn(input_line, "\n")] = 0; input_line[strcspn(input_line, "\n")] = 0;
if (strlen(input_line) == 0) { // Check for ESC/cancel
// Empty input - launch file manager to browse for files if (is_escape_input(input_line)) {
char selected_file[512]; printf("Returning to main menu...\n");
if (launch_file_manager(get_files_directory(), selected_file, sizeof(selected_file)) != 0) { return 0;
printf("Error: Could not launch file manager\n");
return 1;
} }
// Generate smart default output filename with files directory and use enhanced input function // Trim leading whitespace to handle pasted content better
char temp_default[512]; char* trimmed_input = input_line;
char default_output[512]; while (*trimmed_input == ' ' || *trimmed_input == '\t') {
strncpy(temp_default, selected_file, sizeof(temp_default) - 1); trimmed_input++;
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 // Check for ASCII armor FIRST, before checking for empty input
get_default_file_path(temp_default, default_output, sizeof(default_output)); // This handles cases where pasted text starts with the header
if (strncmp(trimmed_input, "-----BEGIN OTP MESSAGE-----", 27) == 0) {
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 // Looks like ASCII armor - collect the full message
char full_message[MAX_INPUT_SIZE * 4] = {0}; char full_message[MAX_INPUT_SIZE * 4] = {0};
strcat(full_message, input_line); strcat(full_message, input_line);
@@ -372,17 +390,31 @@ int handle_decrypt_menu(void) {
return decrypt_text(NULL, full_message); return decrypt_text(NULL, full_message);
} }
else { else if (strlen(trimmed_input) == 0) {
// Check if it looks like a file path // Empty input - launch file manager to browse for files
if (access(input_line, R_OK) == 0) { char selected_file[512];
// It's a valid file - decrypt it with enhanced input for output filename if (launch_file_manager(get_files_directory(), selected_file, sizeof(selected_file)) != 0) {
printf("File browsing cancelled or failed.\n");
printf("Returning to main menu...\n");
return 0;
}
// Generate smart default output filename with files directory and use enhanced input function
char temp_default[512]; char temp_default[512];
char default_output[512]; char default_output[512];
strncpy(temp_default, input_line, sizeof(temp_default) - 1); strncpy(temp_default, selected_file, sizeof(temp_default) - 1);
temp_default[sizeof(temp_default) - 1] = '\0'; temp_default[sizeof(temp_default) - 1] = '\0';
// Remove common encrypted extensions to get a better default // Remove common encrypted extensions to get a better default
if (strstr(temp_default, ".otp.asc")) { if (strstr(temp_default, ".tar.gz.otp")) {
// Directory archive - remove .tar.gz.otp to get original directory name
char* ext_pos = strstr(temp_default, ".tar.gz.otp");
*ext_pos = '\0';
} else if (strstr(temp_default, ".tar.otp")) {
// Directory archive without compression - remove .tar.otp
char* ext_pos = strstr(temp_default, ".tar.otp");
*ext_pos = '\0';
} else if (strstr(temp_default, ".otp.asc")) {
// Replace .otp.asc with original extension or no extension // Replace .otp.asc with original extension or no extension
char* ext_pos = strstr(temp_default, ".otp.asc"); char* ext_pos = strstr(temp_default, ".otp.asc");
*ext_pos = '\0'; *ext_pos = '\0';
@@ -404,7 +436,81 @@ int handle_decrypt_menu(void) {
return 1; return 1;
} }
return decrypt_file(input_line, output_file); // Check if it's a directory archive
if (strstr(selected_file, ".tar.gz.otp") || strstr(selected_file, ".tar.otp")) {
// It's a directory archive - extract to directory
char extract_dir[512];
strncpy(extract_dir, output_file, sizeof(extract_dir) - 1);
extract_dir[sizeof(extract_dir) - 1] = '\0';
// Remove .tar.gz.otp or .tar.otp extension to get directory name
char* ext = strstr(extract_dir, ".tar.gz.otp");
if (!ext) ext = strstr(extract_dir, ".tar.otp");
if (ext) *ext = '\0';
printf("Extracting directory archive to: %s/\n", extract_dir);
return decrypt_and_extract_directory(selected_file, extract_dir);
} else {
return decrypt_file(selected_file, output_file);
}
}
else {
// Check if it looks like a file path
if (access(trimmed_input, 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, trimmed_input, sizeof(temp_default) - 1);
temp_default[sizeof(temp_default) - 1] = '\0';
// Remove common encrypted extensions to get a better default
if (strstr(temp_default, ".tar.gz.otp")) {
// Directory archive - remove .tar.gz.otp to get original directory name
char* ext_pos = strstr(temp_default, ".tar.gz.otp");
*ext_pos = '\0';
} else if (strstr(temp_default, ".tar.otp")) {
// Directory archive without compression - remove .tar.otp
char* ext_pos = strstr(temp_default, ".tar.otp");
*ext_pos = '\0';
} else 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;
}
// Check if it's a directory archive
if (strstr(trimmed_input, ".tar.gz.otp") || strstr(trimmed_input, ".tar.otp")) {
// It's a directory archive - extract to directory
char extract_dir[512];
strncpy(extract_dir, output_file, sizeof(extract_dir) - 1);
extract_dir[sizeof(extract_dir) - 1] = '\0';
// Remove .tar.gz.otp or .tar.otp extension to get directory name
char* ext = strstr(extract_dir, ".tar.gz.otp");
if (!ext) ext = strstr(extract_dir, ".tar.otp");
if (ext) *ext = '\0';
printf("Extracting directory archive to: %s/\n", extract_dir);
return decrypt_and_extract_directory(trimmed_input, extract_dir);
} else {
return decrypt_file(trimmed_input, output_file);
}
} else { } else {
printf("Input not recognized as ASCII armor or valid file path.\n"); printf("Input not recognized as ASCII armor or valid file path.\n");
return 1; return 1;
@@ -472,14 +578,24 @@ int handle_file_encrypt(void) {
printf("\nSelect output format:\n"); printf("\nSelect output format:\n");
printf(" 1. Binary (.otp) - preserves file permissions\n"); printf(" 1. Binary (.otp) - preserves file permissions\n");
printf(" 2. ASCII (.otp.asc) - text-safe format\n"); printf(" 2. ASCII (.otp.asc) - text-safe format\n");
printf("Enter choice (1-2): "); printf("Enter choice (1-2) or 'esc' to cancel: ");
char format_input[10]; char format_input[10];
if (!fgets(format_input, sizeof(format_input), stdin)) { if (!fgets(format_input, sizeof(format_input), stdin)) {
printf("Error: Failed to read input\n"); printf("Error: Failed to read input\n");
free(selected_pad);
return 1; return 1;
} }
format_input[strcspn(format_input, "\n")] = 0;
// Check for ESC/cancel
if (is_escape_input(format_input)) {
printf("Returning to main menu...\n");
free(selected_pad);
return 0;
}
int ascii_armor = (atoi(format_input) == 2) ? 1 : 0; int ascii_armor = (atoi(format_input) == 2) ? 1 : 0;
// Generate default output filename // Generate default output filename
@@ -503,3 +619,301 @@ int handle_file_encrypt(void) {
free(selected_pad); free(selected_pad);
return result; return result;
} }
// Comparison function for qsort (case-insensitive)
static int compare_dir_strings(const void *a, const void *b) {
return strcasecmp(*(const char**)a, *(const char**)b);
}
// Function to get list of subdirectories
static int get_subdirs(const char *path, char ***subdirs) {
DIR *dir = opendir(path);
if (!dir) return 0;
int count = 0;
int capacity = 10;
*subdirs = malloc(capacity * sizeof(char*));
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_DIR) {
// Skip . and ..
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
if (count >= capacity) {
capacity *= 2;
*subdirs = realloc(*subdirs, capacity * sizeof(char*));
}
(*subdirs)[count++] = strdup(entry->d_name);
}
}
closedir(dir);
// Sort alphabetically
if (count > 0) {
qsort(*subdirs, count, sizeof(char*), compare_dir_strings);
}
return count;
}
// Function to get a single keypress without echo
static int getch_nav(void) {
struct termios oldt, newt;
int ch;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return ch;
}
// Navigate directories with arrow keys
static char* navigate_directory_interactive(void) {
char current_path[PATH_MAX];
char original_path[PATH_MAX];
getcwd(original_path, sizeof(original_path));
getcwd(current_path, sizeof(current_path));
char **subdirs = NULL;
int subdir_count = 0;
int current_index = 0;
printf("\nNavigate with arrow keys (UP/DOWN: cycle, RIGHT: enter, LEFT: back, ENTER: select, Q: cancel)\n");
printf("\033[?25l"); // Hide cursor
while (1) {
// Clear line and show current path
printf("\r\033[K%s", current_path);
// If we have subdirectories and an index, show current selection
if (subdir_count > 0 && current_index < subdir_count) {
printf("/%s", subdirs[current_index]);
}
fflush(stdout);
int ch = getch_nav();
// Handle arrow keys (they come as escape sequences)
if (ch == 27) { // ESC sequence
getch_nav(); // Skip '['
ch = getch_nav();
if (ch == 'A') { // UP arrow
// Load subdirs if not loaded
if (subdirs == NULL) {
subdir_count = get_subdirs(current_path, &subdirs);
current_index = 0;
} else if (subdir_count > 0) {
current_index = (current_index - 1 + subdir_count) % subdir_count;
}
}
else if (ch == 'B') { // DOWN arrow
// Load subdirs if not loaded
if (subdirs == NULL) {
subdir_count = get_subdirs(current_path, &subdirs);
current_index = 0;
} else if (subdir_count > 0) {
current_index = (current_index + 1) % subdir_count;
}
}
else if (ch == 'C') { // RIGHT arrow - go deeper
if (subdir_count > 0 && current_index < subdir_count) {
// Navigate into selected directory
char new_path[PATH_MAX];
snprintf(new_path, sizeof(new_path), "%s/%s", current_path, subdirs[current_index]);
if (chdir(new_path) == 0) {
getcwd(current_path, sizeof(current_path));
// Free old subdirs
for (int i = 0; i < subdir_count; i++) {
free(subdirs[i]);
}
free(subdirs);
subdirs = NULL;
// Load subdirs of new directory and show first one
subdir_count = get_subdirs(current_path, &subdirs);
current_index = 0;
}
}
}
else if (ch == 'D') { // LEFT arrow - go up
if (chdir("..") == 0) {
getcwd(current_path, sizeof(current_path));
// Free old subdirs
for (int i = 0; i < subdir_count; i++) {
free(subdirs[i]);
}
free(subdirs);
subdirs = NULL;
subdir_count = 0;
current_index = 0;
}
}
}
else if (ch == '\n' || ch == '\r') { // ENTER - confirm selection
// If a subdirectory is displayed, navigate into it first
if (subdir_count > 0 && current_index < subdir_count) {
char new_path[PATH_MAX];
snprintf(new_path, sizeof(new_path), "%s/%s", current_path, subdirs[current_index]);
if (chdir(new_path) == 0) {
getcwd(current_path, sizeof(current_path));
}
}
break;
}
else if (ch == 'q' || ch == 'Q') { // Q to quit
printf("\033[?25h\n"); // Show cursor
// Restore original directory
chdir(original_path);
// Clean up
for (int i = 0; i < subdir_count; i++) {
free(subdirs[i]);
}
free(subdirs);
return NULL;
}
}
printf("\033[?25h\n"); // Show cursor
// Clean up
for (int i = 0; i < subdir_count; i++) {
free(subdirs[i]);
}
free(subdirs);
// Restore original directory before returning
char* result = strdup(current_path);
chdir(original_path);
return result;
}
int handle_directory_encrypt(void) {
printf("\n");
print_centered_header("Directory Encrypt", 0);
// Directory selection options
printf("\nDirectory selection options:\n");
printf(" 1. Type directory path directly\n");
printf(" 2. Navigate with arrow keys\n");
printf(" 3. Use file manager (navigate to directory)\n");
printf("Enter choice (1-3) or 'esc' to cancel: ");
char choice_input[10];
char dir_path[512];
if (!fgets(choice_input, sizeof(choice_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
choice_input[strcspn(choice_input, "\n")] = 0;
// Check for ESC/cancel
if (is_escape_input(choice_input)) {
printf("Returning to main menu...\n");
return 0;
}
int choice = atoi(choice_input);
if (choice == 2) {
// Use arrow key navigation
char* selected = navigate_directory_interactive();
if (!selected) {
printf("Directory selection cancelled.\n");
return 0;
}
strncpy(dir_path, selected, sizeof(dir_path) - 1);
dir_path[sizeof(dir_path) - 1] = '\0';
free(selected);
printf("Selected: %s\n", dir_path);
}
else if (choice == 3) {
// Use directory manager
if (launch_directory_manager(".", dir_path, sizeof(dir_path)) != 0) {
printf("Falling back to manual directory path entry.\n");
printf("Enter directory path to encrypt (or 'esc' to cancel): ");
if (!fgets(dir_path, sizeof(dir_path), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
dir_path[strcspn(dir_path, "\n")] = 0;
// Check for ESC/cancel
if (is_escape_input(dir_path)) {
printf("Returning to main menu...\n");
return 0;
}
}
} else {
// Direct directory path input
printf("Enter directory path to encrypt (or 'esc' to cancel): ");
if (!fgets(dir_path, sizeof(dir_path), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
dir_path[strcspn(dir_path, "\n")] = 0;
// Check for ESC/cancel
if (is_escape_input(dir_path)) {
printf("Returning to main menu...\n");
return 0;
}
}
// Check if directory exists
struct stat st;
if (stat(dir_path, &st) != 0 || !S_ISDIR(st.st_mode)) {
printf("Error: '%s' is not a valid directory\n", dir_path);
return 1;
}
// Select pad
char* selected_pad = select_pad_interactive("Select Pad for Directory Encryption",
"Select pad (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("Directory encryption cancelled.\n");
return 1;
}
// Generate default output filename - append .tar.gz.otp to the directory path
char default_output[1024];
// Remove trailing slash if present
char clean_path[512];
strncpy(clean_path, dir_path, sizeof(clean_path) - 1);
clean_path[sizeof(clean_path) - 1] = '\0';
size_t path_len = strlen(clean_path);
if (path_len > 0 && clean_path[path_len - 1] == '/') {
clean_path[path_len - 1] = '\0';
}
snprintf(default_output, sizeof(default_output), "%s.tar.gz.otp", clean_path);
// Get 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");
free(selected_pad);
return 1;
}
// Encrypt directory
int result = encrypt_directory(dir_path, selected_pad, output_file);
free(selected_pad);
return result;
}

View File

@@ -19,6 +19,7 @@
// Global variables for preferences // Global variables for preferences
static char default_pad_path[1024] = ""; static char default_pad_path[1024] = "";
static char pads_directory[1024] = "";
void show_progress(uint64_t current, uint64_t total, time_t start_time) { void show_progress(uint64_t current, uint64_t total, time_t start_time) {
time_t now = time(NULL); time_t now = time(NULL);
@@ -161,7 +162,8 @@ int launch_text_editor(const char* initial_content, char* result_buffer, size_t
char* get_preferred_file_manager(void) { char* get_preferred_file_manager(void) {
// Try file managers in order of preference // Try file managers in order of preference
const char* file_managers[] = {"ranger", "fzf", "nnn", "lf", NULL}; // fzf is first because it's more intuitive with fuzzy search
const char* file_managers[] = {"fzf", "ranger", "nnn", "lf", NULL};
for (int i = 0; file_managers[i] != NULL; i++) { for (int i = 0; file_managers[i] != NULL; i++) {
char command[512]; char command[512];
@@ -177,7 +179,8 @@ char* get_preferred_file_manager(void) {
int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size) { int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size) {
char* fm = get_preferred_file_manager(); char* fm = get_preferred_file_manager();
if (!fm) { if (!fm) {
printf("No file manager found. Please install ranger, fzf, nnn, or lf.\n"); printf("No file manager found. Please install fzf, ranger, nnn, or lf.\n");
printf("Recommended: sudo apt install fzf\n");
printf("Falling back to manual file path entry.\n"); printf("Falling back to manual file path entry.\n");
return 1; // Fall back to manual entry return 1; // Fall back to manual entry
} }
@@ -190,6 +193,13 @@ int launch_file_manager(const char* start_directory, char* selected_file, size_t
printf("Opening %s for file selection...\n", fm); printf("Opening %s for file selection...\n", fm);
// Show helpful instructions based on file manager
if (strcmp(fm, "fzf") == 0) {
printf("Instructions: Type to search, use arrow keys, press Enter to select\n");
} else if (strcmp(fm, "ranger") == 0) {
printf("Instructions: Arrow keys or j/k to navigate, Enter or l to select, q to quit\n");
}
if (strcmp(fm, "ranger") == 0) { if (strcmp(fm, "ranger") == 0) {
snprintf(command, sizeof(command), "cd '%s' && ranger --choosefile=%s", snprintf(command, sizeof(command), "cd '%s' && ranger --choosefile=%s",
start_directory ? start_directory : ".", temp_filename); start_directory ? start_directory : ".", temp_filename);
@@ -240,6 +250,103 @@ int launch_file_manager(const char* start_directory, char* selected_file, size_t
return 1; // Fall back to manual entry return 1; // Fall back to manual entry
} }
int launch_directory_manager(const char* start_directory, char* selected_dir, size_t buffer_size) {
char* fm = get_preferred_file_manager();
if (!fm) {
printf("No file manager found. Please install fzf, ranger, nnn, or lf.\n");
printf("Recommended: sudo apt install fzf\n");
printf("Falling back to manual directory path entry.\n");
return 1; // Fall back to manual entry
}
char temp_filename[64];
snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_dir_%ld.tmp", time(NULL));
char command[512];
int result = 1;
printf("Opening %s for directory selection...\n", fm);
// Show helpful instructions based on file manager
if (strcmp(fm, "fzf") == 0) {
printf("Instructions: Type to search, use arrow keys, press Enter to select directory\n");
} else if (strcmp(fm, "ranger") == 0) {
printf("Instructions: Navigate INTO the directory, then press 'q' to quit and select it\n");
}
if (strcmp(fm, "ranger") == 0) {
snprintf(command, sizeof(command), "cd '%s' && ranger --choosedir=%s",
start_directory ? start_directory : ".", temp_filename);
} else if (strcmp(fm, "fzf") == 0) {
// fzf doesn't have directory-only mode easily, use find
snprintf(command, sizeof(command), "cd '%s' && find . -type d | 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 directory from temp file
FILE* temp_file = fopen(temp_filename, "r");
if (temp_file) {
if (fgets(selected_dir, buffer_size, temp_file)) {
// Remove trailing newline
selected_dir[strcspn(selected_dir, "\n\r")] = 0;
// For relative paths, make absolute if needed
if (selected_dir[0] == '.' && selected_dir[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_dir + 2);
strncpy(selected_dir, abs_path, buffer_size - 1);
selected_dir[buffer_size - 1] = '\0';
}
} else if (selected_dir[0] != '/') {
// Relative path without ./
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_dir);
strncpy(selected_dir, abs_path, buffer_size - 1);
selected_dir[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
}
// Helper function to check if input contains ESC key
int is_escape_input(const char* input) {
// Check for ESC character (ASCII 27) or empty input after ESC
if (input && (input[0] == 27 || (input[0] == '\0' && strlen(input) == 0))) {
return 1;
}
// Also check for literal "esc" or "ESC" typed
if (input && (strcasecmp(input, "esc") == 0 || strcasecmp(input, "q") == 0)) {
return 1;
}
return 0;
}
// Stdin detection functions implementation // Stdin detection functions implementation
int has_stdin_data(void) { int has_stdin_data(void) {
// Check if stdin is a pipe/redirect (not a terminal) // Check if stdin is a pipe/redirect (not a terminal)
@@ -429,6 +536,11 @@ int load_preferences(void) {
if (strcmp(key, "default_pad") == 0) { if (strcmp(key, "default_pad") == 0) {
strncpy(default_pad_path, value, sizeof(default_pad_path) - 1); strncpy(default_pad_path, value, sizeof(default_pad_path) - 1);
default_pad_path[sizeof(default_pad_path) - 1] = '\0'; default_pad_path[sizeof(default_pad_path) - 1] = '\0';
} else if (strcmp(key, "pads_directory") == 0) {
strncpy(pads_directory, value, sizeof(pads_directory) - 1);
pads_directory[sizeof(pads_directory) - 1] = '\0';
// Apply the pads directory from config
set_current_pads_dir(pads_directory);
} }
} }
} }
@@ -1032,6 +1144,12 @@ int save_preferences(void) {
fprintf(file, "# OTP Preferences File\n"); fprintf(file, "# OTP Preferences File\n");
fprintf(file, "# This file is automatically generated and updated by the OTP program\n\n"); fprintf(file, "# This file is automatically generated and updated by the OTP program\n\n");
// Save pads directory
const char* current_pads = get_current_pads_dir();
if (current_pads && strlen(current_pads) > 0) {
fprintf(file, "pads_directory=%s\n", current_pads);
}
if (strlen(default_pad_path) > 0) { if (strlen(default_pad_path) > 0) {
fprintf(file, "default_pad=%s\n", default_pad_path); fprintf(file, "default_pad=%s\n", default_pad_path);
} }

BIN
tests/test_chacha20_extended Executable file

Binary file not shown.

View File

@@ -0,0 +1,263 @@
/*
* test_chacha20_extended.c - Test ChaCha20 extended counter implementation
*
* This test verifies that the extended counter properly handles:
* 1. Counter overflow at 2^32 blocks (256GB boundary)
* 2. Correct keystream generation across the overflow boundary
* 3. No duplicate keystream blocks
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "../src/nostr_chacha20.h"
#define TEST_BLOCK_SIZE 64
#define BLOCKS_NEAR_OVERFLOW 10 // Test blocks around overflow point
// Test helper: Compare two blocks for equality
int blocks_equal(const uint8_t* block1, const uint8_t* block2, size_t len) {
return memcmp(block1, block2, len) == 0;
}
// Test 1: Verify extended counter handles overflow correctly
int test_counter_overflow() {
printf("Test 1: Counter overflow handling\n");
printf(" Testing counter transition from 0xFFFFFFFF to 0x00000000...\n");
uint8_t key[32];
uint8_t nonce[8];
uint8_t input[TEST_BLOCK_SIZE];
uint8_t output1[TEST_BLOCK_SIZE];
uint8_t output2[TEST_BLOCK_SIZE];
uint8_t output3[TEST_BLOCK_SIZE];
// Initialize test data
memset(key, 0xAA, 32);
memset(nonce, 0xBB, 8);
memset(input, 0, TEST_BLOCK_SIZE);
// Test at counter_low = 0xFFFFFFFE, counter_high = 0
uint32_t counter_low = 0xFFFFFFFE;
uint32_t counter_high = 0;
printf(" Block at counter_low=0xFFFFFFFE, counter_high=0...\n");
if (chacha20_encrypt_extended(key, counter_low, counter_high, nonce,
input, output1, TEST_BLOCK_SIZE) != 0) {
printf(" ❌ FAILED: Error at counter_low=0xFFFFFFFE\n");
return 1;
}
// Test at counter_low = 0xFFFFFFFF, counter_high = 0
counter_low = 0xFFFFFFFF;
printf(" Block at counter_low=0xFFFFFFFF, counter_high=0...\n");
if (chacha20_encrypt_extended(key, counter_low, counter_high, nonce,
input, output2, TEST_BLOCK_SIZE) != 0) {
printf(" ❌ FAILED: Error at counter_low=0xFFFFFFFF\n");
return 1;
}
// Test at counter_low = 0x00000000, counter_high = 1 (after overflow)
counter_low = 0x00000000;
counter_high = 1;
printf(" Block at counter_low=0x00000000, counter_high=1...\n");
if (chacha20_encrypt_extended(key, counter_low, counter_high, nonce,
input, output3, TEST_BLOCK_SIZE) != 0) {
printf(" ❌ FAILED: Error at counter_low=0x00000000, counter_high=1\n");
return 1;
}
// Verify all three blocks are different (no keystream reuse)
if (blocks_equal(output1, output2, TEST_BLOCK_SIZE)) {
printf(" ❌ FAILED: Blocks at 0xFFFFFFFE and 0xFFFFFFFF are identical!\n");
return 1;
}
if (blocks_equal(output2, output3, TEST_BLOCK_SIZE)) {
printf(" ❌ FAILED: Blocks at 0xFFFFFFFF,0 and 0x00000000,1 are identical!\n");
return 1;
}
if (blocks_equal(output1, output3, TEST_BLOCK_SIZE)) {
printf(" ❌ FAILED: Blocks at 0xFFFFFFFE,0 and 0x00000000,1 are identical!\n");
return 1;
}
printf(" ✓ All blocks are unique across overflow boundary\n");
printf(" ✓ PASSED\n\n");
return 0;
}
// Test 2: Simulate processing data that crosses 256GB boundary
int test_large_file_simulation() {
printf("Test 2: Large file simulation (256GB+ boundary)\n");
printf(" Simulating processing across 256GB boundary...\n");
uint8_t key[32];
uint8_t nonce[8];
uint8_t input[1024];
uint8_t output[1024];
// Initialize test data
memset(key, 0x55, 32);
memset(nonce, 0x77, 8);
for (int i = 0; i < 1024; i++) {
input[i] = i & 0xFF;
}
// Simulate being at 256GB - 512 bytes (just before overflow)
// 256GB = 2^32 blocks * 64 bytes = 274,877,906,944 bytes
// Block number at 256GB - 512 bytes = 2^32 - 8 blocks
uint32_t counter_low = 0xFFFFFFF8; // 2^32 - 8
uint32_t counter_high = 0;
printf(" Processing 1KB starting at block 0xFFFFFFF8 (256GB - 512 bytes)...\n");
// This should cross the overflow boundary
int result = chacha20_encrypt_extended(key, counter_low, counter_high, nonce,
input, output, 1024);
if (result != 0) {
printf(" ❌ FAILED: Error processing data across 256GB boundary\n");
return 1;
}
printf(" ✓ Successfully processed data across 256GB boundary\n");
printf(" ✓ PASSED\n\n");
return 0;
}
// Test 3: Verify extended vs standard ChaCha20 compatibility
int test_compatibility() {
printf("Test 3: Compatibility with standard ChaCha20\n");
printf(" Verifying extended mode matches standard mode when counter_high=0...\n");
uint8_t key[32];
uint8_t nonce_standard[12];
uint8_t nonce_reduced[8];
uint8_t input[TEST_BLOCK_SIZE];
uint8_t output_standard[TEST_BLOCK_SIZE];
uint8_t output_extended[TEST_BLOCK_SIZE];
// Initialize test data
memset(key, 0x33, 32);
memset(nonce_standard, 0x44, 12);
memcpy(nonce_reduced, nonce_standard + 4, 8); // Extract last 8 bytes
memset(input, 0, TEST_BLOCK_SIZE);
uint32_t counter = 42;
// Standard ChaCha20
if (chacha20_encrypt(key, counter, nonce_standard, input,
output_standard, TEST_BLOCK_SIZE) != 0) {
printf(" ❌ FAILED: Standard ChaCha20 error\n");
return 1;
}
// Extended ChaCha20 with counter_high=0 and matching nonce
// The extended version builds nonce as [counter_high][nonce_reduced]
// So we need to ensure the first 4 bytes of nonce_standard are 0
uint8_t nonce_standard_zero[12] = {0};
memcpy(nonce_standard_zero + 4, nonce_reduced, 8);
if (chacha20_encrypt(key, counter, nonce_standard_zero, input,
output_standard, TEST_BLOCK_SIZE) != 0) {
printf(" ❌ FAILED: Standard ChaCha20 error\n");
return 1;
}
if (chacha20_encrypt_extended(key, counter, 0, nonce_reduced, input,
output_extended, TEST_BLOCK_SIZE) != 0) {
printf(" ❌ FAILED: Extended ChaCha20 error\n");
return 1;
}
// Compare outputs
if (!blocks_equal(output_standard, output_extended, TEST_BLOCK_SIZE)) {
printf(" ❌ FAILED: Extended mode output differs from standard mode\n");
printf(" First 16 bytes of standard: ");
for (int i = 0; i < 16; i++) printf("%02x ", output_standard[i]);
printf("\n First 16 bytes of extended: ");
for (int i = 0; i < 16; i++) printf("%02x ", output_extended[i]);
printf("\n");
return 1;
}
printf(" ✓ Extended mode matches standard mode when counter_high=0\n");
printf(" ✓ PASSED\n\n");
return 0;
}
// Test 4: Stress test - verify no errors at extreme counter values
int test_extreme_values() {
printf("Test 4: Extreme counter values\n");
printf(" Testing at various extreme counter positions...\n");
uint8_t key[32];
uint8_t nonce[8];
uint8_t input[TEST_BLOCK_SIZE];
uint8_t output[TEST_BLOCK_SIZE];
memset(key, 0x99, 32);
memset(nonce, 0x66, 8);
memset(input, 0, TEST_BLOCK_SIZE);
// Test various extreme positions
struct {
uint32_t counter_low;
uint32_t counter_high;
const char* description;
} test_cases[] = {
{0x00000000, 0, "Start of first 256GB segment"},
{0xFFFFFFFF, 0, "End of first 256GB segment"},
{0x00000000, 1, "Start of second 256GB segment"},
{0xFFFFFFFF, 1, "End of second 256GB segment"},
{0x00000000, 0xFFFF, "Start of segment 65535"},
{0xFFFFFFFF, 0xFFFF, "End of segment 65535"},
};
for (size_t i = 0; i < sizeof(test_cases) / sizeof(test_cases[0]); i++) {
printf(" Testing: %s (0x%08X, 0x%08X)...\n",
test_cases[i].description,
test_cases[i].counter_low,
test_cases[i].counter_high);
if (chacha20_encrypt_extended(key, test_cases[i].counter_low,
test_cases[i].counter_high, nonce,
input, output, TEST_BLOCK_SIZE) != 0) {
printf(" ❌ FAILED at %s\n", test_cases[i].description);
return 1;
}
}
printf(" ✓ All extreme values handled correctly\n");
printf(" ✓ PASSED\n\n");
return 0;
}
int main() {
printf("=================================================================\n");
printf("ChaCha20 Extended Counter Test Suite\n");
printf("=================================================================\n\n");
int failures = 0;
failures += test_counter_overflow();
failures += test_large_file_simulation();
failures += test_compatibility();
failures += test_extreme_values();
printf("=================================================================\n");
if (failures == 0) {
printf("✓ ALL TESTS PASSED\n");
printf("=================================================================\n");
printf("\nThe extended counter implementation is working correctly.\n");
printf("It can now handle pads larger than 256GB without overflow errors.\n");
return 0;
} else {
printf("❌ %d TEST(S) FAILED\n", failures);
printf("=================================================================\n");
return 1;
}
}

129
tests/test_padding.sh Executable file
View File

@@ -0,0 +1,129 @@
#!/bin/bash
# Test script for message padding implementation
set -e
echo "=== Testing Message Padding Implementation ==="
echo ""
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Test counter
TESTS_PASSED=0
TESTS_FAILED=0
# Function to run a test
run_test() {
local test_name="$1"
local test_command="$2"
echo -n "Testing: $test_name... "
if eval "$test_command" > /dev/null 2>&1; then
echo -e "${GREEN}PASS${NC}"
((TESTS_PASSED++))
return 0
else
echo -e "${RED}FAIL${NC}"
((TESTS_FAILED++))
return 1
fi
}
# Create a small test pad if it doesn't exist
if [ ! -f "pads/"*.pad ]; then
echo "Creating test pad (1MB)..."
./build/otp-x86_64 generate 1MB
echo ""
fi
# Get the first pad checksum
PAD_CHKSUM=$(ls pads/*.pad | head -n 1 | xargs basename | sed 's/.pad$//')
echo "Using pad: ${PAD_CHKSUM:0:16}..."
echo ""
# Test 1: Encrypt and decrypt a short message (should be padded to 256 bytes)
echo "Test 1: Short message (10 bytes -> 256 bytes padded)"
TEST_MSG="Hello Test"
ENCRYPTED=$(echo "$TEST_MSG" | ./build/otp-x86_64 encrypt ${PAD_CHKSUM:0:8})
DECRYPTED=$(echo "$ENCRYPTED" | ./build/otp-x86_64 decrypt)
if [ "$DECRYPTED" = "$TEST_MSG" ]; then
echo -e "${GREEN}✓ Short message encryption/decryption successful${NC}"
((TESTS_PASSED++))
else
echo -e "${RED}✗ Short message failed${NC}"
echo " Expected: $TEST_MSG"
echo " Got: $DECRYPTED"
((TESTS_FAILED++))
fi
echo ""
# Test 2: Encrypt and decrypt a medium message (should be padded to 512 bytes)
echo "Test 2: Medium message (~300 bytes -> 512 bytes padded)"
TEST_MSG=$(printf 'A%.0s' {1..300})
ENCRYPTED=$(echo "$TEST_MSG" | ./build/otp-x86_64 encrypt ${PAD_CHKSUM:0:8})
DECRYPTED=$(echo "$ENCRYPTED" | ./build/otp-x86_64 decrypt)
if [ "$DECRYPTED" = "$TEST_MSG" ]; then
echo -e "${GREEN}✓ Medium message encryption/decryption successful${NC}"
((TESTS_PASSED++))
else
echo -e "${RED}✗ Medium message failed${NC}"
((TESTS_FAILED++))
fi
echo ""
# Test 3: Encrypt and decrypt with special characters
echo "Test 3: Special characters and unicode"
TEST_MSG="Hello! @#$%^&*() 测试"
ENCRYPTED=$(echo "$TEST_MSG" | ./build/otp-x86_64 encrypt ${PAD_CHKSUM:0:8})
DECRYPTED=$(echo "$ENCRYPTED" | ./build/otp-x86_64 decrypt)
if [ "$DECRYPTED" = "$TEST_MSG" ]; then
echo -e "${GREEN}✓ Special characters encryption/decryption successful${NC}"
((TESTS_PASSED++))
else
echo -e "${RED}✗ Special characters failed${NC}"
echo " Expected: $TEST_MSG"
echo " Got: $DECRYPTED"
((TESTS_FAILED++))
fi
echo ""
# Test 4: File encryption/decryption with padding
echo "Test 4: File encryption/decryption"
TEST_FILE="/tmp/otp_test_file.txt"
echo "This is a test file for OTP encryption with padding." > "$TEST_FILE"
./build/otp-x86_64 -f "$TEST_FILE" ${PAD_CHKSUM:0:8} -a -o /tmp/test_encrypted.otp.asc
./build/otp-x86_64 decrypt /tmp/test_encrypted.otp.asc -o /tmp/test_decrypted.txt
if diff "$TEST_FILE" /tmp/test_decrypted.txt > /dev/null 2>&1; then
echo -e "${GREEN}✓ File encryption/decryption successful${NC}"
((TESTS_PASSED++))
else
echo -e "${RED}✗ File encryption/decryption failed${NC}"
((TESTS_FAILED++))
fi
# Cleanup
rm -f "$TEST_FILE" /tmp/test_encrypted.otp.asc /tmp/test_decrypted.txt
echo ""
# Summary
echo "=== Test Summary ==="
echo -e "Tests passed: ${GREEN}${TESTS_PASSED}${NC}"
echo -e "Tests failed: ${RED}${TESTS_FAILED}${NC}"
echo ""
if [ $TESTS_FAILED -eq 0 ]; then
echo -e "${GREEN}All tests passed!${NC}"
exit 0
else
echo -e "${RED}Some tests failed.${NC}"
exit 1
fi