pit
change readme, replace system() with spawn() add secure_open() wrapper
aa6f9c7a4237173901493147666ebdcd2297a628
SM <seb.michalk@gmail.com>
2026-01-04 11:12:50 +0000
pit | Bin 46432 -> 0 bytes pit.c | 324 +++++++++++++++++++++++++++++++------------------------------- pit.o | Bin 37016 -> 0 bytes readme.md | 164 ++++++++----------------------- 4 files changed, 201 insertions(+), 287 deletions(-) diff --git a/pit b/pit deleted file mode 100755 index 84d1f21..0000000 Binary files a/pit and /dev/null differ diff --git a/pit.c b/pit.c index e592ae1..85b966c 100644 --- a/pit.c +++ b/pit.c @@ -24,7 +24,7 @@ #include <termios.h> #include <unistd.h> -/* arbitrary sizes */ +/* sizes */ #define PIT_BLOCK_SIZE 4096 #define KEY_SIZE 32 /* 256 bit */ #define SALT_SIZE 32 @@ -35,12 +35,28 @@ #define MOUNTPOINT_PREFIX "/mnt/pit-" #define FS_TYPE "ext4" +#define PASSWORD_MAX 1024 +#define WRAP_NONCE_SIZE crypto_secretbox_NONCEBYTES +#define WRAP_MAC_SIZE crypto_secretbox_MACBYTES +#define KEYFILE_PAYLOAD_SIZE (KEY_SIZE + WRAP_MAC_SIZE) +#define KEYFILE_SIZE (SALT_SIZE + WRAP_NONCE_SIZE + KEYFILE_PAYLOAD_SIZE) + /* cipher conf */ #define CIPHER "aes" #define CIPHER_MODE "xts-plain64" #define HASH "sha256" -/* types */ +struct pwhash_level { + unsigned long long opslimit; + size_t memlimit; +}; + +static const struct pwhash_level pwhash_levels[] = { + { crypto_pwhash_OPSLIMIT_SENSITIVE, crypto_pwhash_MEMLIMIT_SENSITIVE }, + { crypto_pwhash_OPSLIMIT_MODERATE, crypto_pwhash_MEMLIMIT_MODERATE }, + { crypto_pwhash_OPSLIMIT_MIN, crypto_pwhash_MEMLIMIT_MIN } +}; + typedef struct Pit { char *path; size_t size; @@ -48,17 +64,19 @@ typedef struct Pit { int ismounted; } Pit; -/* function declarations, kept the names more verbose here */ +static const size_t npwhash_levels = sizeof(pwhash_levels) / sizeof(pwhash_levels[0]); + static void die(const char *fmt, ...); static void usage(void); static int init_sec_mem(void); +static void *xrealloc(void *p, size_t len); static void *secure_alloc(size_t size); static void secure_free(void *ptr, size_t size); static void term_cleanup(int signo); static int read_password(char *password, size_t size, const char *prompt); static int exec_cmd(const char *fmt, ...); static const char *get_username(void); -static int check_sudo_tool(const char *tool); +static int check_priv_esc(const char *tool); static int run_privileged(const char *fmt, ...); static int generate_key(const char *keyfile); static int read_key_file(const char *path, char **key); @@ -68,7 +86,7 @@ static int cleanup_stale_device(const char *name); static int setup_device_mapper(const char *path, const char *key); static int teardown_device_mapper(const char *path); static int check_filesystem(const char *device); -static int debug_fs_info(const char *device); /* this can be removed if not needed, just used for verbose printing */ +static int debug_fs_info(const char *device); static int create_filesystem(const char *device); static int ensure_mount_dir(void); static int create_mount_point(const char *path); @@ -76,7 +94,7 @@ static int mount_filesystem(const char *device, const char *mountpoint); static int unmount_filesystem(const char *mountpoint); static int open_pit(const char *path, const char *keyfile); static int close_pit(const char *path); -static int list_pits(void); /* this is not used */ +static int list_pits(void); static int find_mounted_pits(char ***paths, int *count); static int panic_close(void); @@ -117,40 +135,44 @@ init_sec_mem(void) } } - if (mlockall(MCL_CURRENT | MCL_FUTURE) < 0){ - fprintf(stderr, "pit: warning: couldn't lock memory pages: %s\n", - strerror(errno)); - fprintf(stderr, "pit: sensitive data might be swapped to disk\n"); - return -1; - } + if (mlockall(MCL_CURRENT | MCL_FUTURE) < 0) + die("pit: couldn't lock memory pages: %s\n", strerror(errno)); return 0; } +static void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("pit: realloc: %s\n", strerror(errno)); + + return p; +} + static void * secure_alloc(size_t size) { void *ptr; - pagesize = sysconf(_SC_PAGESIZE); - if (pagesize < 0) { - fprintf(stderr, "pit: could not get system page size: %s\n", - strerror(errno)); + if (pagesize <= 0) { + pagesize = sysconf(_SC_PAGESIZE); + if (pagesize < 0) + die("pit: could not get system page size: %s\n", strerror(errno)); } - if (posix_memalign(&ptr, pagesize, size) != 0) { - return NULL; - } + if (posix_memalign(&ptr, pagesize, size) != 0) + die("pit: posix_memalign failed: %s\n", strerror(errno)); if (mlock(ptr, size) < 0) { free(ptr); - return NULL; + die("pit: mlock failed: %s\n", strerror(errno)); } if (madvise(ptr, size, MADV_DONTDUMP) < 0) { munlock(ptr, size); free(ptr); - return NULL; + die("pit: madvise failed: %s\n", strerror(errno)); } return ptr; @@ -166,7 +188,6 @@ secure_free(void *ptr, size_t size) } } -/* todo: format this better and more clearly */ static void usage(void) { @@ -262,7 +283,7 @@ get_username(void) } static int -check_sudo_tool(const char *tool) +check_priv_esc(const char *tool) { char path[PATH_MAX]; snprintf(path, sizeof(path), "/usr/bin/%s", tool); @@ -284,9 +305,9 @@ run_privileged(const char *fmt, ...) return exec_cmd("%s", cmd); } - if (check_sudo_tool("doas")) + if (check_priv_esc("doas")) sudo_tool = "doas"; - else if (check_sudo_tool("sudo")) + else if (check_priv_esc("sudo")) sudo_tool = "sudo"; if (!sudo_tool) { @@ -315,35 +336,30 @@ run_privileged(const char *fmt, ...) static int generate_key(const char *keyfile) { - unsigned char *key = secure_alloc(KEY_SIZE); + unsigned char *master = secure_alloc(KEY_SIZE); unsigned char *salt = secure_alloc(SALT_SIZE); - unsigned char *encrypted = secure_alloc(KEY_SIZE + SALT_SIZE); - char *password = secure_alloc(1024); - char *verify = secure_alloc(1024); + unsigned char *nonce = secure_alloc(WRAP_NONCE_SIZE); + unsigned char *wrapping_key = secure_alloc(KEY_SIZE); + unsigned char *filebuf = secure_alloc(KEYFILE_SIZE); + char *password = secure_alloc(PASSWORD_MAX); + char *verify = secure_alloc(PASSWORD_MAX); int pwlen; - int ret = -1; /* def to error */ - int pwhash_result = -1; + int fd = -1; + int ret = -1; + size_t i; + int derived = -1; - if (!key || !salt || !encrypted || !password || !verify) { - fprintf(stderr, "pit: failed to allocate secure memory\n"); - goto cleanup; - } - - if (sodium_init() < 0) { - fprintf(stderr, "pit: failed to initialize sodium\n"); - goto cleanup; - } - - randombytes_buf(key, KEY_SIZE); + randombytes_buf(master, KEY_SIZE); randombytes_buf(salt, SALT_SIZE); + randombytes_buf(nonce, WRAP_NONCE_SIZE); - pwlen = read_password(password, 1024, "Enter password for key encryption (no echo): "); + pwlen = read_password(password, PASSWORD_MAX, "Enter password for key encryption (no echo): "); if (pwlen <= 0) { fprintf(stderr, "pit: failed to read password\n"); goto cleanup; } - if (read_password(verify, 1024, "Verify password: ") <= 0) { + if (read_password(verify, PASSWORD_MAX, "Verify password: ") <= 0) { fprintf(stderr, "pit: failed to read password verification\n"); goto cleanup; } @@ -353,70 +369,67 @@ generate_key(const char *keyfile) goto cleanup; } - /* Try with SENSITIVE settings first */ - pwhash_result = crypto_pwhash( - encrypted + SALT_SIZE, KEY_SIZE, - password, pwlen, - salt, - crypto_pwhash_OPSLIMIT_SENSITIVE, - crypto_pwhash_MEMLIMIT_SENSITIVE, - crypto_pwhash_ALG_DEFAULT); - - /* If SENSITIVE fails, try MODERATE */ - if (pwhash_result != 0) { - fprintf(stderr, "pit: key derivation failed with sensitive memory settings - trying with moderate...\n"); - pwhash_result = crypto_pwhash( - encrypted + SALT_SIZE, KEY_SIZE, - password, pwlen, - salt, - crypto_pwhash_OPSLIMIT_MODERATE, - crypto_pwhash_MEMLIMIT_MODERATE, - crypto_pwhash_ALG_DEFAULT); + for (i = 0; i < npwhash_levels; i++) { + if (crypto_pwhash(wrapping_key, KEY_SIZE, + password, pwlen, + salt, + pwhash_levels[i].opslimit, + pwhash_levels[i].memlimit, + crypto_pwhash_ALG_DEFAULT) == 0) { + derived = 0; + break; + } } - /* If MODERATE fails, try MINIMAL */ - if (pwhash_result != 0) { - fprintf(stderr, "pit: key derivation with moderate memory settings failed - trying minimal...\n"); - pwhash_result = crypto_pwhash( - encrypted + SALT_SIZE, KEY_SIZE, - password, pwlen, - salt, - crypto_pwhash_OPSLIMIT_MIN, - crypto_pwhash_MEMLIMIT_MIN, - crypto_pwhash_ALG_DEFAULT); + if (derived != 0) { + fprintf(stderr, "pit: key derivation failed - insufficient memory\n"); + goto cleanup; } - /* If all attempts failed */ - if (pwhash_result != 0) { - fprintf(stderr, "pit: key derivation failed - system has insufficient memory\n"); + memcpy(filebuf, salt, SALT_SIZE); + memcpy(filebuf + SALT_SIZE, nonce, WRAP_NONCE_SIZE); + + if (crypto_secretbox_easy(filebuf + SALT_SIZE + WRAP_NONCE_SIZE, + master, KEY_SIZE, nonce, wrapping_key) != 0) { + fprintf(stderr, "pit: failed to encrypt key material\n"); goto cleanup; } - /* If we reached here, one of the pwhash attempts succeeded */ - memcpy(encrypted, salt, SALT_SIZE); - - FILE *f = fopen(keyfile, "wb"); - if (!f) { + fd = open(keyfile, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) { fprintf(stderr, "pit: cannot create key file: %s\n", strerror(errno)); goto cleanup; } - if (fwrite(encrypted, 1, KEY_SIZE + SALT_SIZE, f) != KEY_SIZE + SALT_SIZE) { - fprintf(stderr, "pit: failed to write key file: %s\n", strerror(errno)); - fclose(f); - goto cleanup; + size_t total = 0; + while (total < KEYFILE_SIZE) { + ssize_t written = write(fd, filebuf + total, KEYFILE_SIZE - total); + if (written < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "pit: failed to write key file: %s\n", strerror(errno)); + goto cleanup; + } + if (written == 0) { + fprintf(stderr, "pit: failed to write key file: short write\n"); + goto cleanup; + } + total += written; } - fclose(f); printf("pit: key generated successfully\n"); - ret = 0; // success + ret = 0; cleanup: - secure_free(key, KEY_SIZE); + if (fd >= 0) + close(fd); + secure_free(master, KEY_SIZE); secure_free(salt, SALT_SIZE); - secure_free(encrypted, KEY_SIZE + SALT_SIZE); - secure_free(password, 1024); - secure_free(verify, 1024); + secure_free(nonce, WRAP_NONCE_SIZE); + secure_free(filebuf, KEYFILE_SIZE); + secure_free(wrapping_key, KEY_SIZE); + secure_free(password, PASSWORD_MAX); + secure_free(verify, PASSWORD_MAX); return ret; } @@ -463,82 +476,78 @@ create_pit(const char *path, size_t size) static int read_key_file(const char *path, char **key) { - FILE *f; - unsigned char *encrypted = secure_alloc(KEY_SIZE + SALT_SIZE); + int fd; + struct stat st; + unsigned char *filebuf = secure_alloc(KEYFILE_SIZE); unsigned char *decrypted = secure_alloc(KEY_SIZE); - char *password = secure_alloc(1024); + unsigned char *wrapping_key = secure_alloc(KEY_SIZE); + char *password = secure_alloc(PASSWORD_MAX); int pwlen; - int ret = -1; /* see above */ + int ret = -1; + size_t i; + int success = 0; + ssize_t nread; - if (!encrypted || !decrypted || !password) { - fprintf(stderr, "pit: failed to allocate secure memory\n"); - goto cleanup; - } + if (stat(path, &st) < 0) + die("pit: cannot stat key file %s: %s\n", path, strerror(errno)); + if (st.st_size != (off_t)KEYFILE_SIZE) + die("pit: invalid key file size\n"); - f = fopen(path, "r"); - if (!f) { + fd = open(path, O_RDONLY); + if (fd < 0) die("pit: cannot open key file %s: %s\n", path, strerror(errno)); - } - if (fread(encrypted, 1, KEY_SIZE + SALT_SIZE, f) != KEY_SIZE + SALT_SIZE) { - fclose(f); + nread = read(fd, filebuf, KEYFILE_SIZE); + if (nread != (ssize_t)KEYFILE_SIZE) { + close(fd); die("pit: invalid key file size\n"); } - fclose(f); + close(fd); + fd = -1; fflush(stdout); - if (read_password(password, 1024, "Enter password for key (no echo): ") <= 0) { + pwlen = read_password(password, PASSWORD_MAX, "Enter password for key (no echo): "); + if (pwlen <= 0) { fprintf(stderr, "pit: failed to read password\n"); goto cleanup; } - pwlen = strlen(password); - if (pwlen > 0 && password[pwlen-1] == '\n') - password[--pwlen] = 0; - - int r = crypto_pwhash( - decrypted, KEY_SIZE, - password, pwlen, - encrypted, - crypto_pwhash_OPSLIMIT_MIN, - crypto_pwhash_MEMLIMIT_MIN, - crypto_pwhash_ALG_DEFAULT); - - if (r != 0) { - fprintf(stderr, "pit: trying with moderate memory settings ... \n"); - r = crypto_pwhash( - decrypted, KEY_SIZE, - password, pwlen, - encrypted, - crypto_pwhash_OPSLIMIT_MODERATE, - crypto_pwhash_MEMLIMIT_MODERATE, - crypto_pwhash_ALG_DEFAULT); + for (i = 0; i < npwhash_levels; i++) { + if (crypto_pwhash(wrapping_key, KEY_SIZE, + password, pwlen, + filebuf, + pwhash_levels[i].opslimit, + pwhash_levels[i].memlimit, + crypto_pwhash_ALG_DEFAULT) != 0) + continue; + + if (crypto_secretbox_open_easy(decrypted, + filebuf + SALT_SIZE + WRAP_NONCE_SIZE, + KEYFILE_PAYLOAD_SIZE, + filebuf + SALT_SIZE, + wrapping_key) == 0) { + success = 1; + break; + } } - if (r !=0) { - fprintf(stderr, "pit: trying with sensitive memory settings ...\n"); - r = crypto_pwhash( - decrypted, KEY_SIZE, - password, pwlen, - encrypted, - crypto_pwhash_OPSLIMIT_SENSITIVE, - crypto_pwhash_MEMLIMIT_SENSITIVE, - crypto_pwhash_ALG_DEFAULT); - } - - if (r != 0) { + if (!success) { fprintf(stderr, "pit: key derivation failed - insufficient memory or wrong password\n"); goto cleanup; } - *key = (char*)decrypted; + *key = (char *)decrypted; decrypted = NULL; - ret = 0; /* success, otherwise ret = -1 */ + ret = 0; cleanup: - secure_free(encrypted, KEY_SIZE + SALT_SIZE); - if (decrypted) secure_free(decrypted, KEY_SIZE); - secure_free(password, 1024); + if (fd >= 0) + close(fd); + secure_free(filebuf, KEYFILE_SIZE); + if (decrypted) + secure_free(decrypted, KEY_SIZE); + secure_free(wrapping_key, KEY_SIZE); + secure_free(password, PASSWORD_MAX); return ret; } @@ -712,7 +721,6 @@ check_filesystem(const char *device) if (bytes_read < 2) return 0; - /* check ext4 superblock magic number at offset 0x38 (56) */ return (buf[0x38] == 0x53 && buf[0x39] == 0xEF); } @@ -756,7 +764,7 @@ open_pit(const char *path, const char *keyfile) return -1; r = setup_device_mapper(path, key); - free(key); + secure_free(key, KEY_SIZE); if (r < 0) die("pit: failed to setup device mapper\n"); @@ -765,7 +773,7 @@ open_pit(const char *path, const char *keyfile) if (!mapper_path) die("pit: failed to get mapper path\n"); - /* only create filesystem if one doesn't exist, otherwise it erases the data */ + /* only create filesystem if one doesn't exist */ printf("pit: checking fs on %s\n", mapper_path); if (!check_filesystem(mapper_path)) { printf("pit: no filesystem detected, creating new one\n"); @@ -775,7 +783,7 @@ open_pit(const char *path, const char *keyfile) } } else { printf("pit: existing filesystem found\n"); - //debug_fs_info(mapper_path); + /* debug_fs_info(mapper_path); */ } /* Create mount point */ @@ -798,7 +806,6 @@ open_pit(const char *path, const char *keyfile) return 0; } -/* todo .. */ static int list_pits(void) { @@ -981,7 +988,6 @@ close_pit(const char *path) return 0; } -/* this is a bit hacky, might need improvements, for now it works */ static int find_mounted_pits(char ***paths, int *count) { @@ -1002,16 +1008,7 @@ find_mounted_pits(char ***paths, int *count) if (!space) continue; - char **new_list = realloc(list, (n + 1) * sizeof(char *)); - if (!new_list) { - fprintf(stderr, "pit: out of memory\n"); - fclose(mtab); - for (int i = 0; i < n; i++) - free(list[i]); - free(list); - return -1; - } - list = new_list; + list = xrealloc(list, (n + 1) * sizeof(char *)); *space = '\0'; space++; @@ -1023,12 +1020,11 @@ find_mounted_pits(char ***paths, int *count) list[n] = strdup(mountpoint); if (!list[n]) { - fprintf(stderr, "pit: out of memory\n"); fclose(mtab); for (int i = 0; i < n; i++) free(list[i]); free(list); - return -1; + die("pit: strdup failed: %s\n", strerror(errno)); } n++; } @@ -1083,7 +1079,7 @@ panic_close(void) } } closedir(proc_dir); - usleep(100000); /* small delay for processes to die */ + usleep(100000); } /* Force unmount */ @@ -1187,7 +1183,7 @@ main(int argc, char *argv[]) if (!strcmp(argv[1], "list")) { if (argc != 2) usage(); - return list_pits(); /* this is not implemented as of now */ + return list_pits(); } if (!strcmp(argv[1], "panic")) { if (argc != 2) diff --git a/pit.o b/pit.o deleted file mode 100644 index 61a42f9..0000000 Binary files a/pit.o and /dev/null differ diff --git a/readme.md b/readme.md index 4ca0a01..3920cae 100644 --- a/readme.md +++ b/readme.md @@ -1,137 +1,55 @@ -# pit - create and manage encrypted containers +pit - encrypted containers +========================== -Need a simple way to keep your data private? `pit` is a minimalist encrypted container manager written in C. -Create encrypted containers, toss your files in, and lock them up. It's like `tomb` but way more minimal - about ~1000 lines of C code. +pit creates, opens and closes single-file containers backed by LUKS. +Code is small enough to read and has no scripting layer. -## Why would you want this? +features +-------- +- containers are plain files formatted as ext4 through dm-crypt/LUKS +- key files hold a random 256-bit master key wrapped with a password +- memory for secrets is page aligned, locked and wiped +- "panic" kills processes using pit mounts and detaches everything -Ever needed to: +usage +----- -- Store your private keys and passwords securely? -- Have a secure place for your none of your business stuff? -- Just want a simple, trustworthy way to encrypt files? + # create 100 MB container file and matching key + pit dig vault.pit 100 + pit key vault.key -`pit` gives you encrypted containers without the complexity. No fancy features, no bloat, no Qt and the like - just secure storage that you can actually understand and audit. If you're the kind of person who appreciates simple, well-written tools and cares about security without going full paranoid, this might be for you. + # open and close (needs root for the dm/luks operations) + pit open vault.pit vault.key + pit close vault.pit -Think of it as a minimal, no-nonsense approach to file encryption. It won't protect you from three-letter agencies, as that might require hw locked keys etc. but it'll keep your sensitive files secure from most threats while being simple enough that you can read and understand the entire code. + # emergency close every mount and mapper entry + pit panic -## Overview +A key file is bound to the container that was first formatted with it. +Reuse only if you intentionally want identical LUKS keys. -- Written in C (~ 1000 LOC) -- Simple, auditable codebase -- Inspired by tomb, but a more minimal approach -- No shell scripts -- An attempt was made to not suck +security notes +-------------- +- AES-XTS with a libsodium-derived key (Argon2) protects the container +- master key never touches disk outside the encrypted key file +- stdin echo is disabled while entering passwords +- program aborts if secure memory cannot be locked -## Features +security limits +--------------- +- assumes your swap, firmware and boot chain are trusted +- does not prevent physical attacks or hardware keyloggers +- losing the key file or password means permanent data loss -- Single file containers with LUKS encryption -- Password protected key files using libsodium -- Protected memory handling -- Random data container creation -- Emergency panic close feature -- Root escalation handling (sudo/doas) +requirements +------------ -## Security - -### What pit is: -- A simple and auditable encrypted container manager -- Secure storage for sensitive files -- Based on proven cryptographic standards (LUKS, AES-XTS, libsodium) -- Protected against memory attacks - -### What pit is not: -- Military grade encryption solution -- Plausible deniability storage -- Multi-user security system -- Steganography tool - -### Security Features -- LUKS containers with AES-XTS-PLAIN64 cipher -- Password-based key derivation using Argon2 (via libsodium) -- Secure memory handling (locked pages, memory wiping) -- Hidden password input -- Random data container creation -- Emergency container closing ```pit panic``` (panic mode) - -### Security Limitations -- No swap space verification -- No hardware security module support -- No key backup mechanism -- No filesystem integrity verification - -## Usage - -```bash -# Create a new 100MB container and key -pit dig container.pit 100 -pit key container.key - -# Open container with key (requires root) -pit open container.pit container.key - -# Close container -pit close container.pit - -# Emergency close all containers -pit panic -``` - -## Requirements -- libsodium - libcryptsetup -- sudo or doas - -#### Debian/Ubuntu -```sudo apt update && sudo apt install libsodium-dev libcryptsetup-dev``` - -#### Red Hat/CentOS/Fedora -```sudo dnf install libsodium-devel cryptsetup-devel``` - -#### Arch Linux -```sudo pacman -S libsodium cryptsetup``` - -#### Gentoo -```sudo emerge libsodium cryptsetup``` - -#### Alpine Linux -```sudo apk add libsodium-dev cryptsetup-dev``` - -## Security Considerations - -1. your system should have: - - Encrypted swap or no swap - - Full disk encryption - - Protected boot process - -2. pit doesn't protect against: - - Physical memory attacks - - Evil maid attacks - - Hardware keyloggers - -3. key files are protected but: - - Eventually need secure storage - - No backup mechanism - - No recovery option if lost - -## Comparison to other tools -- Pure C implementation vs shell scripts -- More minimal (~1000 LOC vs ~4000 or more, no GUI dependencies) -- Fewer features but more auditable -- No steganography or advanced features -- Focus on core container operations - -## Installation -```bash -make -sudo make install -``` +- libsodium +- a privilege escalation helper (sudo or doas) for non-root use -## To do -- Make pit for the truly paranoid -- Add swap space verification -- Perhaps add backing up keys -- Fs integrity checks +build +----- -## License -MIT + make + sudo make install