229 lines
7.0 KiB
C
229 lines
7.0 KiB
C
/*
|
|
* make_test_pad.c — generate a small test OTP pad on a target directory.
|
|
*
|
|
* Bit-compatible with the `otp` project's pad format:
|
|
* - <chksum>.pad : raw random bytes, first 32 bytes are the "reserved header"
|
|
* (also used as the key to encrypt the checksum).
|
|
* - <chksum>.state : text file "offset=32\n" (32-byte header reserved).
|
|
* - chksum is the 64-hex-char XOR checksum as computed by
|
|
* otp/src/crypto.c:calculate_checksum (position-dependent XOR folded into
|
|
* 32 buckets, then XORed with the first 32 pad bytes).
|
|
*
|
|
* Usage:
|
|
* make_test_pad <pad_dir> <size_bytes>
|
|
*
|
|
* Example:
|
|
* make_test_pad /media/user/USBDISK/pads 1048576
|
|
*
|
|
* Entropy source: /dev/urandom (local-entropy test path only — NOT for production
|
|
* pads; production pads should use the `otp` CLI with keyboard/TRNG entropy or a
|
|
* future hardware signer).
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
|
|
#define CHKSUM_HEX_LEN 64
|
|
#define CHKSUM_BIN_LEN 32
|
|
#define HEADER_RESERVED 32
|
|
#define BUF_SIZE (64 * 1024)
|
|
|
|
static int compute_checksum(const char *pad_path, char *checksum_hex) {
|
|
FILE *file = fopen(pad_path, "rb");
|
|
if (!file) {
|
|
fprintf(stderr, "compute_checksum: cannot open %s: %s\n",
|
|
pad_path, strerror(errno));
|
|
return 1;
|
|
}
|
|
|
|
unsigned char checksum[CHKSUM_BIN_LEN];
|
|
unsigned char buffer[BUF_SIZE];
|
|
size_t bytes_read;
|
|
size_t total_bytes = 0;
|
|
|
|
memset(checksum, 0, CHKSUM_BIN_LEN);
|
|
|
|
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
|
|
for (size_t i = 0; i < bytes_read; i++) {
|
|
size_t pos = total_bytes + i;
|
|
unsigned char bucket = (unsigned char)(pos % CHKSUM_BIN_LEN);
|
|
checksum[bucket] ^= buffer[i] ^
|
|
(unsigned char)((pos >> 8) & 0xFF) ^
|
|
(unsigned char)((pos >> 16) & 0xFF) ^
|
|
(unsigned char)((pos >> 24) & 0xFF);
|
|
}
|
|
total_bytes += bytes_read;
|
|
}
|
|
fclose(file);
|
|
|
|
/* XOR the checksum with the first 32 bytes of the pad (the "pad key"). */
|
|
file = fopen(pad_path, "rb");
|
|
if (!file) {
|
|
fprintf(stderr, "compute_checksum: cannot reopen %s: %s\n",
|
|
pad_path, strerror(errno));
|
|
return 1;
|
|
}
|
|
unsigned char pad_key[CHKSUM_BIN_LEN];
|
|
if (fread(pad_key, 1, CHKSUM_BIN_LEN, file) != CHKSUM_BIN_LEN) {
|
|
fprintf(stderr, "compute_checksum: pad too small for header key\n");
|
|
fclose(file);
|
|
return 1;
|
|
}
|
|
fclose(file);
|
|
|
|
unsigned char encrypted_checksum[CHKSUM_BIN_LEN];
|
|
for (int i = 0; i < CHKSUM_BIN_LEN; i++) {
|
|
encrypted_checksum[i] = checksum[i] ^ pad_key[i];
|
|
}
|
|
|
|
for (int i = 0; i < CHKSUM_BIN_LEN; i++) {
|
|
sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]);
|
|
}
|
|
checksum_hex[CHKSUM_HEX_LEN] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
static int write_random(const char *path, size_t size) {
|
|
int urand = open("/dev/urandom", O_RDONLY);
|
|
if (urand < 0) {
|
|
fprintf(stderr, "cannot open /dev/urandom: %s\n", strerror(errno));
|
|
return 1;
|
|
}
|
|
|
|
int out = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
if (out < 0) {
|
|
fprintf(stderr, "cannot create %s: %s\n", path, strerror(errno));
|
|
close(urand);
|
|
return 1;
|
|
}
|
|
|
|
unsigned char buffer[BUF_SIZE];
|
|
size_t written = 0;
|
|
while (written < size) {
|
|
size_t chunk = size - written;
|
|
if (chunk > sizeof(buffer)) chunk = sizeof(buffer);
|
|
|
|
ssize_t got = 0;
|
|
while ((size_t)got < chunk) {
|
|
ssize_t r = read(urand, buffer + got, chunk - (size_t)got);
|
|
if (r < 0) {
|
|
if (errno == EINTR) continue;
|
|
fprintf(stderr, "read urandom failed: %s\n", strerror(errno));
|
|
close(out);
|
|
close(urand);
|
|
return 1;
|
|
}
|
|
if (r == 0) {
|
|
fprintf(stderr, "urandom EOF (unexpected)\n");
|
|
close(out);
|
|
close(urand);
|
|
return 1;
|
|
}
|
|
got += r;
|
|
}
|
|
|
|
ssize_t put = 0;
|
|
while (put < got) {
|
|
ssize_t w = write(out, buffer + put, (size_t)got - (size_t)put);
|
|
if (w < 0) {
|
|
if (errno == EINTR) continue;
|
|
fprintf(stderr, "write %s failed: %s\n", path, strerror(errno));
|
|
close(out);
|
|
close(urand);
|
|
return 1;
|
|
}
|
|
put += w;
|
|
}
|
|
written += (size_t)got;
|
|
}
|
|
|
|
close(out);
|
|
close(urand);
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
if (argc != 3) {
|
|
fprintf(stderr, "Usage: %s <pad_dir> <size_bytes>\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
const char *pad_dir = argv[1];
|
|
size_t size = (size_t)strtoull(argv[2], NULL, 10);
|
|
if (size < 64) {
|
|
fprintf(stderr, "size must be at least 64 bytes\n");
|
|
return 1;
|
|
}
|
|
|
|
/* Ensure pad_dir exists. */
|
|
struct stat st;
|
|
if (stat(pad_dir, &st) != 0) {
|
|
if (mkdir(pad_dir, 0755) != 0) {
|
|
fprintf(stderr, "cannot create %s: %s\n", pad_dir, strerror(errno));
|
|
return 1;
|
|
}
|
|
} else if (!S_ISDIR(st.st_mode)) {
|
|
fprintf(stderr, "%s exists but is not a directory\n", pad_dir);
|
|
return 1;
|
|
}
|
|
|
|
/* Write the pad to a temporary name first, then rename by checksum. */
|
|
char tmp_path[1024];
|
|
snprintf(tmp_path, sizeof(tmp_path), "%s/.tmp_pad_XXXXXX", pad_dir);
|
|
/* mkstemp would be cleaner, but we want a stable name for the rename. */
|
|
int tfd = mkstemp(tmp_path);
|
|
if (tfd < 0) {
|
|
fprintf(stderr, "mkstemp failed: %s\n", strerror(errno));
|
|
return 1;
|
|
}
|
|
close(tfd);
|
|
|
|
if (write_random(tmp_path, size) != 0) {
|
|
unlink(tmp_path);
|
|
return 1;
|
|
}
|
|
|
|
char chksum[CHKSUM_HEX_LEN + 1];
|
|
if (compute_checksum(tmp_path, chksum) != 0) {
|
|
unlink(tmp_path);
|
|
return 1;
|
|
}
|
|
|
|
char final_path[1024];
|
|
char state_path[1024];
|
|
snprintf(final_path, sizeof(final_path), "%s/%s.pad", pad_dir, chksum);
|
|
snprintf(state_path, sizeof(state_path), "%s/%s.state", pad_dir, chksum);
|
|
|
|
if (rename(tmp_path, final_path) != 0) {
|
|
fprintf(stderr, "rename %s -> %s failed: %s\n",
|
|
tmp_path, final_path, strerror(errno));
|
|
unlink(tmp_path);
|
|
return 1;
|
|
}
|
|
|
|
/* Write initial state file: offset=32 (header reserved). */
|
|
FILE *state = fopen(state_path, "w");
|
|
if (!state) {
|
|
fprintf(stderr, "cannot create %s: %s\n", state_path, strerror(errno));
|
|
return 1;
|
|
}
|
|
fprintf(state, "offset=%d\n", HEADER_RESERVED);
|
|
fclose(state);
|
|
|
|
printf("Created test pad:\n");
|
|
printf(" pad: %s\n", final_path);
|
|
printf(" state: %s\n", state_path);
|
|
printf(" size: %zu bytes\n", size);
|
|
printf(" chksum: %s\n", chksum);
|
|
printf(" chksum prefix (16 chars): %.16s\n", chksum);
|
|
return 0;
|
|
}
|