214 lines
7.2 KiB
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;
|
|
}
|