Compare commits

..

13 Commits

Author SHA1 Message Date
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
14 changed files with 1193 additions and 97 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 -Isrc/miniz -Isrc/microtar
CFLAGS_MINIZ = -Wall -Wextra -std=c99 -D_POSIX_C_SOURCE=200112L -Isrc -Isrc/miniz -Isrc/microtar -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 src/miniz/*.c)
MICROTAR_SOURCES = $(wildcard src/microtar/*.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
src/miniz/%.o: src/miniz/%.c
$(CC) $(CFLAGS_MINIZ) -c $< -o $@
# Compile microtar library files normally
src/microtar/%.o: src/microtar/%.c
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) -c $< -o $@
clean: clean:

View File

@@ -58,14 +58,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.47/otp-v0.3.47-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.47/otp-v0.3.47-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.47-linux-x86_64 otp
chmod +x otp chmod +x otp
# Run it # Run it
@@ -431,6 +431,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.

493
src/archive.c Normal file
View File

@@ -0,0 +1,493 @@
#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/microtar.h"
// Suppress warnings from miniz header
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#include "miniz/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
rename(temp_decrypted, temp_tar);
}
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) {
@@ -464,7 +459,7 @@ int encrypt_text(const char* pad_identifier, const char* input_text) {
// Use universal ASCII armor generator // Use universal ASCII armor generator
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, input_len, &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);
@@ -592,36 +587,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; // Skip integrity check - trust the filename checksum
} else if (mode == DECRYPT_MODE_INTERACTIVE) { if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Warning: Pad integrity check failed!\n"); printf("Using pad: %s\n", stored_chksum);
printf("Expected: %s\n", stored_chksum);
printf("Continue anyway? (y/N): ");
fflush(stdout);
char response[10];
if (fgets(response, sizeof(response), stdin) == NULL ||
(response[0] != 'y' && response[0] != 'Y')) {
printf("Decryption aborted.\n");
return 1;
}
}
} else if (integrity_result != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Cannot verify pad integrity\n");
} else {
printf("Error: Cannot verify pad integrity\n");
}
return 1;
} else {
if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Pad integrity: VERIFIED\n");
}
} }
// Decode base64 ciphertext // Decode base64 ciphertext
@@ -746,7 +719,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];
@@ -791,12 +763,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;
@@ -927,7 +895,7 @@ int encrypt_file(const char* pad_identifier, const char* input_file, const char*
// Use universal ASCII armor generator // Use universal ASCII armor generator
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, file_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 +929,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);
@@ -1165,8 +1133,11 @@ 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");
// Pause before returning to menu to let user see the success message // Only pause if output is not a temporary file (directory decryption uses /tmp/)
print_centered_header("File Decryption Complete", 1); if (strncmp(output_file, "/tmp/", 5) != 0) {
// Pause before returning to menu to let user see the success message
print_centered_header("File Decryption Complete", 1);
}
// Cleanup // Cleanup
free(encrypted_data); free(encrypted_data);

View File

@@ -219,8 +219,22 @@ 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);
@@ -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.47"
// 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
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -314,6 +332,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

@@ -129,8 +129,8 @@ int chacha20_block(const uint8_t key[32], uint32_t counter,
return 0; return 0;
} }
int chacha20_encrypt(const uint8_t key[32], uint32_t counter, 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) {
uint8_t keystream[CHACHA20_BLOCK_SIZE]; uint8_t keystream[CHACHA20_BLOCK_SIZE];
size_t offset = 0; size_t offset = 0;
@@ -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

@@ -63,10 +63,10 @@ int chacha20_block(const uint8_t key[32], uint32_t counter,
/** /**
* ChaCha20 encryption/decryption * ChaCha20 encryption/decryption
* *
* Encrypts or decrypts data using ChaCha20 stream cipher. * Encrypts or decrypts data using ChaCha20 stream cipher.
* Since ChaCha20 is a stream cipher, encryption and decryption are the same operation. * Since ChaCha20 is a stream cipher, encryption and decryption are the same operation.
* *
* @param key[in] 32-byte key * @param key[in] 32-byte key
* @param counter[in] Initial 32-bit counter value * @param counter[in] Initial 32-bit counter value
* @param nonce[in] 12-byte nonce * @param nonce[in] 12-byte nonce
@@ -75,10 +75,29 @@ int chacha20_block(const uint8_t key[32], uint32_t counter,
* @param length[in] Length of input data in bytes * @param length[in] Length of input data in bytes
* @return 0 on success, negative on error * @return 0 on success, negative on error
*/ */
int chacha20_encrypt(const uint8_t key[32], uint32_t counter, 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

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");
*offset = 0; fprintf(stderr, "Please convert old binary state files to text format\n");
return 0; *offset = 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];

155
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;
@@ -125,11 +128,12 @@ void show_main_menu(void) {
print_centered_header(header, 0); print_centered_header(header, 0);
printf("\n"); printf("\n");
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(" \033[4mD\033[0mecrypt\n"); //DECRYPT printf(" Di\033[4mr\033[0mectory encrypt\n"); //DIRECTORY ENCRYPT
printf(" \033[4mP\033[0mads\n"); //PADS printf(" \033[4mD\033[0mecrypt\n"); //DECRYPT
printf(" E\033[4mx\033[0mit\n"); //EXIT printf(" \033[4mP\033[0mads\n"); //PADS
printf(" E\033[4mx\033[0mit\n"); //EXIT
printf("\nSelect option: "); printf("\nSelect option: ");
} }
@@ -330,7 +334,15 @@ int handle_decrypt_menu(void) {
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';
@@ -352,7 +364,23 @@ int handle_decrypt_menu(void) {
return 1; return 1;
} }
return decrypt_file(selected_file, 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 if (strncmp(input_line, "-----BEGIN OTP MESSAGE-----", 27) == 0) { 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
@@ -382,7 +410,15 @@ int handle_decrypt_menu(void) {
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 +440,23 @@ 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(input_line, ".tar.gz.otp") || strstr(input_line, ".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(input_line, extract_dir);
} else {
return decrypt_file(input_line, 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;
@@ -501,5 +553,90 @@ int handle_file_encrypt(void) {
int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor); int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor);
free(selected_pad); free(selected_pad);
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. Use file manager (navigate to directory)\n");
printf("Enter choice (1-2): ");
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;
}
if (atoi(choice_input) == 2) {
// 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: ");
if (!fgets(dir_path, sizeof(dir_path), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
dir_path[strcspn(dir_path, "\n")] = 0;
}
} else {
// Direct directory path input
printf("Enter directory path to encrypt: ");
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 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; return result;
} }

View File

@@ -240,6 +240,83 @@ 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 ranger, fzf, nnn, or lf.\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);
printf("Navigate INTO the directory you want to encrypt, 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
}
// 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)

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;
}
}