/* * test_libotppad.c — unit tests for libotppad. * * Covers: XOR, base64 round-trip, Padmé padding round-trip, ASCII armor * round-trip, binary header round-trip, .state file round-trip, and pad * checksum against a known pad. */ #define _POSIX_C_SOURCE 200809L #include "libotppad.h" #include #include #include #include #include #include static int failures = 0; #define CHECK(cond, msg) do { \ if (!(cond)) { \ fprintf(stderr, "FAIL: %s (%s:%d)\n", (msg), __FILE__, __LINE__); \ failures++; \ } else { \ printf("ok: %s\n", (msg)); \ } \ } while (0) static void test_xor(void) { unsigned char data[] = {0x01, 0x02, 0x03, 0x04, 0x05}; unsigned char pad[] = {0xff, 0xee, 0xdd, 0xcc, 0xbb}; unsigned char out[5]; CHECK(otppad_xor(data, 5, pad, out) == 0, "xor returns 0"); CHECK(out[0] == 0xfe && out[1] == 0xec && out[2] == 0xde && out[3] == 0xc8 && out[4] == 0xbe, "xor values correct"); /* in-place aliasing */ CHECK(otppad_xor(data, 5, pad, data) == 0, "xor in-place"); CHECK(data[0] == 0xfe, "xor in-place value"); } static void test_base64(void) { const char *in = "Hello, World!"; int len = (int)strlen(in); char *enc = otppad_base64_encode((const unsigned char *)in, len); CHECK(enc != NULL, "base64 encode"); int dlen = 0; unsigned char *dec = otppad_base64_decode(enc, &dlen); CHECK(dec != NULL, "base64 decode"); CHECK(dlen == len, "base64 length preserved"); CHECK(memcmp(dec, in, len) == 0, "base64 round-trip"); free(enc); free(dec); /* empty input */ enc = otppad_base64_encode((const unsigned char *)"", 0); CHECK(enc != NULL && strcmp(enc, "") == 0, "base64 empty"); free(enc); } static void test_padding(void) { /* 10-byte message -> 256-byte bucket */ size_t chunk = otppad_chunk_size(10); CHECK(chunk == 256, "chunk size for 10 bytes is 256"); /* 300-byte message -> 512-byte bucket */ chunk = otppad_chunk_size(300); CHECK(chunk == 512, "chunk size for 300 bytes is 512"); /* 256-byte message -> 512 (needs +1 for 0x80) */ chunk = otppad_chunk_size(256); CHECK(chunk == 512, "chunk size for 256 bytes is 512 (needs +1)"); unsigned char buf[512]; const char *msg = "test message"; size_t msg_len = strlen(msg); memset(buf, 0xaa, sizeof(buf)); memcpy(buf, msg, msg_len); chunk = otppad_chunk_size(msg_len); CHECK(otppad_pad_apply(buf, msg_len, chunk) == 0, "pad apply"); CHECK(buf[msg_len] == 0x80, "pad marker at msg_len"); size_t recovered; CHECK(otppad_pad_remove(buf, chunk, &recovered) == 0, "pad remove"); CHECK(recovered == msg_len, "pad remove recovers length"); CHECK(memcmp(buf, msg, msg_len) == 0, "pad round-trip preserves message"); } static void test_armor(void) { const char *chksum = "333e9902db839d9d7f1f6aaa30f392a77c9abd011dd6274d9d3cf167361a789e"; unsigned char ct[] = {0xde, 0xad, 0xbe, 0xef, 0x10, 0x20, 0x30, 0x40}; uint64_t offset = 12345; char *armor = NULL; CHECK(otppad_armor_generate("v0.3.53", chksum, offset, ct, sizeof(ct), &armor) == 0, "armor generate"); CHECK(armor != NULL && strstr(armor, "BEGIN OTP MESSAGE") != NULL, "armor has begin marker"); char parsed_chk[OTPPAD_CHKSUM_HEX_LEN + 1]; uint64_t parsed_off; char parsed_b64[8192]; CHECK(otppad_armor_parse(armor, parsed_chk, &parsed_off, parsed_b64, sizeof(parsed_b64)) == 0, "armor parse"); CHECK(strcmp(parsed_chk, chksum) == 0, "armor chksum round-trip"); CHECK(parsed_off == offset, "armor offset round-trip"); int dlen = 0; unsigned char *dec = otppad_base64_decode(parsed_b64, &dlen); CHECK(dec != NULL && dlen == (int)sizeof(ct), "armor b64 length"); CHECK(dec && memcmp(dec, ct, sizeof(ct)) == 0, "armor ciphertext round-trip"); free(armor); free(dec); } static void test_bin_header(void) { otppad_bin_header_t hdr; memset(&hdr, 0, sizeof(hdr)); memcpy(hdr.magic, OTPPAD_MAGIC, OTPPAD_MAGIC_LEN); hdr.version = OTPPAD_FORMAT_VERSION; memset(hdr.pad_chksum, 0xab, OTPPAD_CHKSUM_BIN_LEN); hdr.pad_offset = 999; hdr.file_mode = 0644; hdr.file_size = 4096; const char *path = "test_bin_header.tmp"; FILE *fp = fopen(path, "wb"); CHECK(fp != NULL, "open bin header tmp for write"); CHECK(otppad_bin_header_write(fp, &hdr) == 0, "bin header write"); fclose(fp); fp = fopen(path, "rb"); CHECK(fp != NULL, "open bin header tmp for read"); otppad_bin_header_t hdr2; CHECK(otppad_bin_header_read(fp, &hdr2) == 0, "bin header read"); fclose(fp); unlink(path); CHECK(hdr2.version == hdr.version, "bin header version"); CHECK(hdr2.pad_offset == hdr.pad_offset, "bin header offset"); CHECK(hdr2.file_mode == hdr.file_mode, "bin header file_mode"); CHECK(hdr2.file_size == hdr.file_size, "bin header file_size"); CHECK(memcmp(hdr2.pad_chksum, hdr.pad_chksum, OTPPAD_CHKSUM_BIN_LEN) == 0, "bin header chksum"); CHECK(otppad_bin_is_magic((const unsigned char *)hdr2.magic, 4), "bin is_magic"); } static void test_state(void) { const char *dir = "test_state_dir.tmp"; mkdir(dir, 0755); const char *chksum = "333e9902db839d9d7f1f6aaa30f392a77c9abd011dd6274d9d3cf167361a789e"; uint64_t off; CHECK(otppad_state_read(dir, chksum, &off) != 0, "state read missing fails"); CHECK(otppad_state_write(dir, chksum, 42) == 0, "state write"); CHECK(otppad_state_read(dir, chksum, &off) == 0, "state read"); CHECK(off == 42, "state value"); CHECK(otppad_state_write(dir, chksum, 1000) == 0, "state overwrite"); CHECK(otppad_state_read(dir, chksum, &off) == 0, "state read 2"); CHECK(off == 1000, "state value 2"); /* cleanup */ char path[1024]; snprintf(path, sizeof(path), "%s/%s.state", dir, chksum); unlink(path); rmdir(dir); } static void test_checksum(void) { /* Build a tiny pad: 64 bytes, first 32 are the "key", rest arbitrary. */ const char *dir = "test_chksum_dir.tmp"; const char *chksum_expected = "333e9902db839d9d7f1f6aaa30f392a77c9abd011dd6274d9d3cf167361a789e"; mkdir(dir, 0755); /* Use the real test pad if it exists; otherwise skip this test. */ const char *real_pad = "/media/user/Music/pads/333e9902db839d9d7f1f6aaa30f392a77c9abd011dd6274d9d3cf167361a789e.pad"; FILE *fp = fopen(real_pad, "rb"); if (!fp) { printf("ok: checksum test skipped (no real pad at %s)\n", real_pad); rmdir(dir); return; } fclose(fp); char chk[OTPPAD_CHKSUM_HEX_LEN + 1]; CHECK(otppad_checksum(real_pad, chk) == 0, "checksum computes"); CHECK(strcmp(chk, chksum_expected) == 0, "checksum matches expected (bit-compatible with otp)"); rmdir(dir); } int main(void) { test_xor(); test_base64(); test_padding(); test_armor(); test_bin_header(); test_state(); test_checksum(); if (failures == 0) { printf("\nALL TESTS PASSED\n"); return 0; } printf("\n%d TEST(S) FAILED\n", failures); return 1; }