Files
n_signer/tools/make_test_pad.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;
}