Files
n_signer/libotppad/test_libotppad.c

214 lines
7.2 KiB
C

/*
* 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 <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
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;
}