/* * make_test_pad.c — generate a small test OTP pad on a target directory. * * Bit-compatible with the `otp` project's pad format: * - .pad : raw random bytes, first 32 bytes are the "reserved header" * (also used as the key to encrypt the checksum). * - .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 * * 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 #include #include #include #include #include #include #include #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 \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; }