diff --git a/CHANGELOG.md b/CHANGELOG.md index a2e4f0f..3e1cc77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.3.0] - 2026-03-15 + +### Added +- **WP2: HMAC-SHA256 code integrity** (`daemon/hmac_sha256.c`): replaces CRC32 with HMAC-SHA256 using per-session 256-bit random key from `/dev/urandom`. Uses OpenSSL one-shot `HMAC()` (not deprecated `HMAC_CTX_*`). Graviton3 ARMv8.4 SHA extensions accelerate computation. +- `daemon/hmac_sha256.h`: public API (`owl_hmac_sha256`, `owl_hmac_generate_key`) +- `tests/test_hmac_sha256.c`: 10 unit tests (TDD) — RFC 4231 known-answer vectors, empty/large input, key change, null inputs, key generation, integrity baseline+check, violation detection +- Buffer-based integrity functions (`owl_integrity_baseline_buffer`, `owl_integrity_check_buffer`) for testable verification without /proc I/O +- `scripts/verify.sh`: HMAC-SHA256 baseline assertion in protected phase, ld_preload_hook baseline assertion + +### Changed +- `daemon/integrity.h`: `baseline_crc` replaced with `baseline_hmac[32]` + `hmac_key[32]` in `struct owl_integrity` +- `daemon/integrity.c`: baseline and check functions use HMAC-SHA256 via buffer variants; CRC32 retained for heartbeat `state_hash` +- `daemon/Makefile`: added `hmac_sha256.c`, linked `-lssl -lcrypto` +- `tests/Makefile`: added `test_hmac_sha256` suite, updated `test_integrity` linking with OpenSSL +- `tests/test_integrity.c`: init test checks HMAC fields instead of CRC32 +- `scripts/verify.sh`: version bumped to v2.3.0 +- `README.md`: updated status (v2.3.0, 125 tests, 14 suites), HMAC-SHA256 in daemon description, removed CRC32 limitation + ## [2.2.0] - 2026-03-15 ### Added diff --git a/README.md b/README.md index ef60b7c..0a91c84 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Runs on Graviton3 (c7g.large), Ubuntu 24.04, kernel 6.17. - **kernel/** - loadable module. Kprobes on ptrace, /proc/pid/mem, process_vm_readv/writev, mmap, module load/unload. ARM64 system register monitoring. Chardev for event delivery. - **ebpf/** - BPF LSM hooks returning -EPERM (ptrace_access_check, file_open for /proc/pid/mem + /dev/mem + /dev/kmem, file_mprotect). Tracepoints. Kprobe on do_init_module. Ring buffer to userspace. -- **daemon/** - epoll on chardev + BPF ring buffer. Policy engine. Signature scanner. CRC32 code integrity. Self-protection watchdog. TracerPid debugger detection. LD_PRELOAD environ scanning. Process ancestry tree for correlation. +- **daemon/** - epoll on chardev + BPF ring buffer. Policy engine. Signature scanner. HMAC-SHA256 code integrity. Self-protection watchdog. TracerPid debugger detection. LD_PRELOAD environ scanning. Process ancestry tree for correlation. - **game/** - ncurses test target. Mutable state, function pointers, exported address. - **cheats/** - 9 attack programs: process_vm_readv, /proc/pid/mem, /dev/mem, ptrace read, ptrace write, process_vm_writev, LD_PRELOAD, mprotect injection, debug registers. - **platform/** - Lambda + API Gateway + DynamoDB telemetry receiver. @@ -18,9 +18,9 @@ Runs on Graviton3 (c7g.large), Ubuntu 24.04, kernel 6.17. ## Status -v2.2.0. 115 unit tests, 13 suites. eBPF LSM returns EPERM on ptrace, /proc/pid/mem, /dev/mem, /dev/kmem, process_vm_writev. Module can't be unloaded while daemon runs. TracerPid polling detects debuggers attached before daemon start. LD_PRELOAD detection on exec. Process tree tracks ancestry for correlation engine. +v2.3.0. 125 unit tests, 14 suites. eBPF LSM returns EPERM on ptrace, /proc/pid/mem, /dev/mem, /dev/kmem, process_vm_writev. Module can't be unloaded while daemon runs. TracerPid polling detects debuggers attached before daemon start. LD_PRELOAD detection on exec. Process tree tracks ancestry for correlation engine. -Prototype limitations: linear signature scan, CRC32 not cryptographic, no fleet management. +Prototype limitations: linear signature scan, no fleet management. ## Getting started diff --git a/daemon/Makefile b/daemon/Makefile index 1a10cd7..d06a7d9 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -12,7 +12,7 @@ CFLAGS := -std=c11 -Wall -Wextra -Werror \ -D_GNU_SOURCE -D_FORTIFY_SOURCE=2 \ $(INCLUDES) $(CFLAGS_EXTRA) \ -I../ebpf -LDFLAGS := -lpthread -lcurl -lbpf -lelf -lz +LDFLAGS := -lpthread -lcurl -lbpf -lelf -lz -lssl -lcrypto DEPFLAGS = -MMD -MP -MF $(@:.o=.d) # Source files @@ -22,6 +22,7 @@ SRCS := main.c \ process_tree.c \ sig_loader.c \ integrity.c \ + hmac_sha256.c \ self_protect.c \ debugger_detect.c \ preload_detect.c \ diff --git a/daemon/hmac_sha256.c b/daemon/hmac_sha256.c new file mode 100644 index 0000000..89a3cbd --- /dev/null +++ b/daemon/hmac_sha256.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * hmac_sha256.c - HMAC-SHA256 via OpenSSL one-shot HMAC() + * + * Uses the non-deprecated HMAC() function (OpenSSL 3.0+). + * Only HMAC_CTX_* is deprecated; the one-shot variant is stable. + */ + +#include +#include + +#include +#include + +#include "hmac_sha256.h" + +int owl_hmac_sha256(const uint8_t *key, size_t key_len, + const uint8_t *data, size_t data_len, + uint8_t *out) +{ + if (!key || !out) + return -1; + if (!data && data_len > 0) + return -1; + + unsigned int md_len = 0; + const uint8_t empty = 0; + const uint8_t *d = data ? data : ∅ + + unsigned char *result = HMAC(EVP_sha256(), key, (int)key_len, + d, data_len, out, &md_len); + if (!result || md_len != OWL_HMAC_SHA256_LEN) + return -1; + + return 0; +} + +int owl_hmac_generate_key(uint8_t *key, size_t key_len) +{ + if (!key || key_len == 0) + return -1; + + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return -1; + + size_t total = 0; + while (total < key_len) { + ssize_t n = read(fd, key + total, key_len - total); + if (n <= 0) { + close(fd); + return -1; + } + total += (size_t)n; + } + + close(fd); + return 0; +} diff --git a/daemon/hmac_sha256.h b/daemon/hmac_sha256.h new file mode 100644 index 0000000..effc3d8 --- /dev/null +++ b/daemon/hmac_sha256.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * hmac_sha256.h - HMAC-SHA256 wrapper for code integrity verification + * + * Uses OpenSSL one-shot HMAC() with EVP_sha256(). + * Per-session 256-bit random key from /dev/urandom. + */ + +#ifndef OWLBEAR_HMAC_SHA256_H +#define OWLBEAR_HMAC_SHA256_H + +#include +#include + +#define OWL_HMAC_SHA256_LEN 32 + +/** + * owl_hmac_sha256 - Compute HMAC-SHA256 of a buffer + * @key: HMAC key + * @key_len: Key length in bytes + * @data: Input data (may be NULL if data_len is 0) + * @data_len: Input data length + * @out: Output buffer (must be at least OWL_HMAC_SHA256_LEN bytes) + * + * Returns 0 on success, -1 on error. + */ +int owl_hmac_sha256(const uint8_t *key, size_t key_len, + const uint8_t *data, size_t data_len, + uint8_t *out); + +/** + * owl_hmac_generate_key - Generate a random key from /dev/urandom + * @key: Output buffer + * @key_len: Number of random bytes to generate + * + * Returns 0 on success, -1 on error. + */ +int owl_hmac_generate_key(uint8_t *key, size_t key_len); + +#endif /* OWLBEAR_HMAC_SHA256_H */ diff --git a/daemon/integrity.c b/daemon/integrity.c index f27e1b3..a9be78f 100644 --- a/daemon/integrity.c +++ b/daemon/integrity.c @@ -2,7 +2,8 @@ /* * integrity.c - Userspace code integrity verification * - * CRC32 hash of game .text segment for runtime code modification detection. + * HMAC-SHA256 of game .text segment for runtime code modification detection. + * CRC32 retained for heartbeat state_hash. */ #include @@ -13,6 +14,7 @@ #include #include "integrity.h" +#include "hmac_sha256.h" /* ------------------------------------------------------------------------- * CRC32 — standard polynomial 0xEDB88320 (reflected) @@ -190,14 +192,20 @@ int owl_integrity_baseline(struct owl_integrity *ctx, pid_t pid) return -1; } - ctx->baseline_crc = owl_crc32(buf, ctx->text_size); - ctx->baseline_set = true; + if (owl_integrity_baseline_buffer(ctx, buf, ctx->text_size) < 0) { + free(buf); + return -1; + } free(buf); - printf("owlbeard: integrity baseline: text=0x%lx size=%lu crc32=0x%08x\n", + char hex[OWL_HMAC_SHA256_LEN * 2 + 1]; + for (size_t i = 0; i < OWL_HMAC_SHA256_LEN; i++) + sprintf(hex + 2 * i, "%02x", ctx->baseline_hmac[i]); + + printf("owlbeard: integrity baseline: text=0x%lx size=%lu hmac=%s\n", (unsigned long)ctx->text_start, (unsigned long)ctx->text_size, - ctx->baseline_crc); + hex); return 0; } @@ -217,10 +225,46 @@ int owl_integrity_check(const struct owl_integrity *ctx) return -1; } - uint32_t current_crc = owl_crc32(buf, ctx->text_size); + int result = owl_integrity_check_buffer(ctx, buf, ctx->text_size); free(buf); - if (current_crc != ctx->baseline_crc) + return result; +} + +/* ------------------------------------------------------------------------- + * Buffer-based integrity functions (testable without /proc I/O) + * ----------------------------------------------------------------------- */ + +int owl_integrity_baseline_buffer(struct owl_integrity *ctx, + const uint8_t *buf, size_t len) +{ + if (!ctx || !buf) + return -1; + + if (owl_hmac_generate_key(ctx->hmac_key, OWL_HMAC_SHA256_LEN) < 0) + return -1; + + if (owl_hmac_sha256(ctx->hmac_key, OWL_HMAC_SHA256_LEN, + buf, len, ctx->baseline_hmac) < 0) + return -1; + + ctx->baseline_set = true; + return 0; +} + +int owl_integrity_check_buffer(const struct owl_integrity *ctx, + const uint8_t *buf, size_t len) +{ + if (!ctx || !buf || !ctx->baseline_set) + return -1; + + uint8_t current[OWL_HMAC_SHA256_LEN]; + + if (owl_hmac_sha256(ctx->hmac_key, OWL_HMAC_SHA256_LEN, + buf, len, current) < 0) + return -1; + + if (memcmp(current, ctx->baseline_hmac, OWL_HMAC_SHA256_LEN) != 0) return 1; /* Integrity violation */ return 0; diff --git a/daemon/integrity.h b/daemon/integrity.h index 20397a1..99c0325 100644 --- a/daemon/integrity.h +++ b/daemon/integrity.h @@ -2,9 +2,10 @@ /* * integrity.h - Userspace code integrity verification * - * Computes a CRC32 hash of the game's .text segment from /proc//mem - * at baseline, then periodically re-checks. Any mismatch indicates - * runtime code modification (code injection, inline hooks, etc.). + * Computes an HMAC-SHA256 of the game's .text segment from /proc//mem + * at baseline using a per-session random key, then periodically re-checks. + * Any mismatch indicates runtime code modification (code injection, + * inline hooks, etc.). */ #ifndef OWLBEAR_INTEGRITY_H @@ -15,12 +16,15 @@ #include #include +#include "hmac_sha256.h" + /* Integrity checker state */ struct owl_integrity { pid_t target_pid; - uint64_t text_start; /* Virtual address of .text */ - uint64_t text_size; /* Size of .text in bytes */ - uint32_t baseline_crc; /* CRC32 captured at baseline */ + uint64_t text_start; /* Virtual address of .text */ + uint64_t text_size; /* Size of .text in bytes */ + uint8_t baseline_hmac[OWL_HMAC_SHA256_LEN]; /* HMAC-SHA256 at baseline */ + uint8_t hmac_key[OWL_HMAC_SHA256_LEN]; /* Per-session random key */ bool baseline_set; }; @@ -31,12 +35,13 @@ struct owl_integrity { void owl_integrity_init_ctx(struct owl_integrity *ctx); /** - * owl_integrity_baseline - Capture baseline .text CRC32 + * owl_integrity_baseline - Capture baseline .text HMAC-SHA256 * @ctx: Integrity context * @pid: Target PID * * Parses /proc//maps to find the first r-xp segment, - * reads it via /proc//mem, and computes CRC32. + * reads it via /proc//mem, and computes HMAC-SHA256 + * with a fresh random key. * * Returns 0 on success, -1 on error. */ @@ -52,6 +57,31 @@ int owl_integrity_baseline(struct owl_integrity *ctx, pid_t pid); */ int owl_integrity_check(const struct owl_integrity *ctx); +/** + * owl_integrity_baseline_buffer - Capture baseline HMAC-SHA256 of a buffer + * @ctx: Integrity context + * @buf: Data buffer + * @len: Buffer length + * + * Generates a random key, computes HMAC-SHA256, stores both. + * Returns 0 on success, -1 on error. + */ +int owl_integrity_baseline_buffer(struct owl_integrity *ctx, + const uint8_t *buf, size_t len); + +/** + * owl_integrity_check_buffer - Verify buffer against stored baseline + * @ctx: Integrity context (must have baseline set) + * @buf: Data buffer + * @len: Buffer length + * + * Returns 0 if buffer matches baseline. + * Returns 1 if buffer has changed (integrity violation). + * Returns -1 on error. + */ +int owl_integrity_check_buffer(const struct owl_integrity *ctx, + const uint8_t *buf, size_t len); + /** * owl_crc32 - Compute CRC32 of a buffer * @buf: Data buffer diff --git a/scripts/verify.sh b/scripts/verify.sh index df2ba36..a9bd754 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -297,7 +297,7 @@ preflight() { | grep -o '"accountId" *: *"[^"]*"' | cut -d'"' -f4 || echo "local") cat > "${OUT_DIR}/summary.txt" <
"${phase_dir}/dmesg_after.txt" dmesg_since "${phase_mark}" "${phase_dir}/dmesg_phase_diff.txt" @@ -855,6 +867,15 @@ phase_protected() { assert_pass "protected/code integrity baseline captured" fi + # Check integrity baseline uses HMAC-SHA256 (not CRC32) + if [ -f "${phase_dir}/daemon_stdout.log" ] && \ + grep -q "hmac=" "${phase_dir}/daemon_stdout.log" 2>/dev/null; then + assert_pass "protected/code integrity uses HMAC-SHA256" + elif [ -f "${phase_dir}/daemon_stdout.log" ] && \ + grep -q "crc32=" "${phase_dir}/daemon_stdout.log" 2>/dev/null; then + assert_fail "protected/code integrity still uses CRC32 (should be HMAC-SHA256)" + fi + stop_daemon # Unload module @@ -980,7 +1001,7 @@ FOOTER main() { echo "" echo -e "${BOLD}================================================${NC}" - echo -e "${BOLD} Owlbear E2E Verification (v2.2.0)${NC}" + echo -e "${BOLD} Owlbear E2E Verification (v2.3.0)${NC}" echo -e "${BOLD} Evidence Package Builder${NC}" echo -e "${BOLD}================================================${NC}" echo "" diff --git a/tests/Makefile b/tests/Makefile index a9721da..adc1d99 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -24,7 +24,8 @@ TEST_BINS := test_events \ test_self_protect \ test_debugger_detect \ test_preload_detect \ - test_process_tree + test_process_tree \ + test_hmac_sha256 .PHONY: all unit integration clean @@ -69,8 +70,8 @@ test_sig_loader: test_harness.o test_sig_loader.o $(DAEMON_DIR)/sig_loader.o $(D test_event_pipeline: test_harness.o test_event_pipeline.o $(DAEMON_DIR)/event_pipeline.o $(DAEMON_DIR)/policy.o $(DAEMON_DIR)/scanner.o $(DAEMON_DIR)/preload_detect.o $(DAEMON_DIR)/process_tree.o $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) -test_integrity: test_harness.o test_integrity.o $(DAEMON_DIR)/integrity.o - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) +test_integrity: test_harness.o test_integrity.o $(DAEMON_DIR)/integrity.o $(DAEMON_DIR)/hmac_sha256.o + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) -lssl -lcrypto test_self_protect: test_harness.o test_self_protect.o $(DAEMON_DIR)/self_protect.o $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) @@ -84,6 +85,9 @@ test_preload_detect: test_harness.o test_preload_detect.o $(DAEMON_DIR)/preload_ test_process_tree: test_harness.o test_process_tree.o $(DAEMON_DIR)/process_tree.o $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) +test_hmac_sha256: test_harness.o test_hmac_sha256.o $(DAEMON_DIR)/hmac_sha256.o $(DAEMON_DIR)/integrity.o + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) -lssl -lcrypto + # Daemon objects needed by tests $(DAEMON_DIR)/%.o: $(DAEMON_DIR)/%.c $(CC) $(CFLAGS) $(DEPFLAGS) -c -o $@ $< @@ -103,7 +107,7 @@ clean: $(RM) $(TEST_BINS) *.o *.d $(RM) $(DAEMON_DIR)/policy.o $(DAEMON_DIR)/scanner.o $(DAEMON_DIR)/heartbeat.o $(RM) $(DAEMON_DIR)/bpf_event_convert.o $(DAEMON_DIR)/sig_loader.o - $(RM) $(DAEMON_DIR)/event_pipeline.o $(DAEMON_DIR)/integrity.o + $(RM) $(DAEMON_DIR)/event_pipeline.o $(DAEMON_DIR)/integrity.o $(DAEMON_DIR)/hmac_sha256.o $(RM) $(DAEMON_DIR)/self_protect.o $(DAEMON_DIR)/debugger_detect.o $(DAEMON_DIR)/preload_detect.o $(DAEMON_DIR)/process_tree.o $(RM) $(DAEMON_DIR)/*.d diff --git a/tests/test_hmac_sha256 b/tests/test_hmac_sha256 new file mode 100755 index 0000000..6263068 Binary files /dev/null and b/tests/test_hmac_sha256 differ diff --git a/tests/test_hmac_sha256.c b/tests/test_hmac_sha256.c new file mode 100644 index 0000000..522b37e --- /dev/null +++ b/tests/test_hmac_sha256.c @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * test_hmac_sha256.c - Tests for HMAC-SHA256 and integrity buffer functions + */ + +#include +#include + +#include "test_harness.h" +#include "hmac_sha256.h" +#include "integrity.h" + +/* Helper: hex string to bytes */ +static void hex_to_bytes(const char *hex, uint8_t *out, size_t out_len) +{ + for (size_t i = 0; i < out_len; i++) { + unsigned int byte; + sscanf(hex + 2 * i, "%02x", &byte); + out[i] = (uint8_t)byte; + } +} + +/* ------------------------------------------------------------------------- + * RFC 4231 known-answer tests + * ----------------------------------------------------------------------- */ + +TEST(hmac_rfc4231_test1) { + /* Key = 0x0b * 20, data = "Hi There" */ + uint8_t key[20]; + memset(key, 0x0b, sizeof(key)); + const uint8_t *data = (const uint8_t *)"Hi There"; + uint8_t out[OWL_HMAC_SHA256_LEN]; + uint8_t expected[OWL_HMAC_SHA256_LEN]; + + hex_to_bytes("b0344c61d8db38535ca8afceaf0bf12b" + "881dc200c9833da726e9376c2e32cff7", + expected, OWL_HMAC_SHA256_LEN); + + int ret = owl_hmac_sha256(key, sizeof(key), data, 8, out); + ASSERT_EQ(ret, 0); + ASSERT_EQ(memcmp(out, expected, OWL_HMAC_SHA256_LEN), 0); +} + +TEST(hmac_rfc4231_test2) { + /* Key = "Jefe", data = "what do ya want for nothing?" */ + const uint8_t *key = (const uint8_t *)"Jefe"; + const uint8_t *data = (const uint8_t *)"what do ya want for nothing?"; + uint8_t out[OWL_HMAC_SHA256_LEN]; + uint8_t expected[OWL_HMAC_SHA256_LEN]; + + hex_to_bytes("5bdcc146bf60754e6a042426089575c7" + "5a003f089d2739839dec58b964ec3843", + expected, OWL_HMAC_SHA256_LEN); + + int ret = owl_hmac_sha256(key, 4, data, 28, out); + ASSERT_EQ(ret, 0); + ASSERT_EQ(memcmp(out, expected, OWL_HMAC_SHA256_LEN), 0); +} + +/* ------------------------------------------------------------------------- + * Edge cases + * ----------------------------------------------------------------------- */ + +TEST(hmac_empty_input) { + uint8_t key[OWL_HMAC_SHA256_LEN]; + memset(key, 0x42, sizeof(key)); + uint8_t out[OWL_HMAC_SHA256_LEN]; + memset(out, 0, sizeof(out)); + + int ret = owl_hmac_sha256(key, sizeof(key), NULL, 0, out); + ASSERT_EQ(ret, 0); + + /* Output should be non-zero (valid HMAC of empty data) */ + int all_zero = 1; + for (size_t i = 0; i < OWL_HMAC_SHA256_LEN; i++) { + if (out[i] != 0) { + all_zero = 0; + break; + } + } + ASSERT_EQ(all_zero, 0); +} + +TEST(hmac_large_input) { + uint8_t key[OWL_HMAC_SHA256_LEN]; + memset(key, 0xBB, sizeof(key)); + + size_t len = 64 * 1024; + uint8_t *data = malloc(len); + ASSERT_TRUE(data != NULL); + memset(data, 0xAA, len); + + uint8_t out[OWL_HMAC_SHA256_LEN]; + int ret = owl_hmac_sha256(key, sizeof(key), data, len, out); + free(data); + ASSERT_EQ(ret, 0); +} + +TEST(hmac_key_change) { + const uint8_t data[] = "same data for both"; + uint8_t key1[OWL_HMAC_SHA256_LEN], key2[OWL_HMAC_SHA256_LEN]; + memset(key1, 0x11, sizeof(key1)); + memset(key2, 0x22, sizeof(key2)); + + uint8_t out1[OWL_HMAC_SHA256_LEN], out2[OWL_HMAC_SHA256_LEN]; + owl_hmac_sha256(key1, sizeof(key1), data, sizeof(data) - 1, out1); + owl_hmac_sha256(key2, sizeof(key2), data, sizeof(data) - 1, out2); + + ASSERT_NE(memcmp(out1, out2, OWL_HMAC_SHA256_LEN), 0); +} + +TEST(hmac_same_data_same_hash) { + uint8_t key[OWL_HMAC_SHA256_LEN]; + memset(key, 0xCC, sizeof(key)); + const uint8_t data[] = "deterministic"; + + uint8_t out1[OWL_HMAC_SHA256_LEN], out2[OWL_HMAC_SHA256_LEN]; + owl_hmac_sha256(key, sizeof(key), data, sizeof(data) - 1, out1); + owl_hmac_sha256(key, sizeof(key), data, sizeof(data) - 1, out2); + + ASSERT_EQ(memcmp(out1, out2, OWL_HMAC_SHA256_LEN), 0); +} + +TEST(hmac_null_inputs) { + uint8_t key[OWL_HMAC_SHA256_LEN]; + memset(key, 0xDD, sizeof(key)); + uint8_t data[] = "test"; + uint8_t out[OWL_HMAC_SHA256_LEN]; + + ASSERT_EQ(owl_hmac_sha256(NULL, sizeof(key), data, 4, out), -1); + ASSERT_EQ(owl_hmac_sha256(key, sizeof(key), NULL, 4, out), -1); + ASSERT_EQ(owl_hmac_sha256(key, sizeof(key), data, 4, NULL), -1); +} + +TEST(hmac_generate_key) { + uint8_t key1[OWL_HMAC_SHA256_LEN] = {0}; + uint8_t key2[OWL_HMAC_SHA256_LEN] = {0}; + + ASSERT_EQ(owl_hmac_generate_key(key1, sizeof(key1)), 0); + ASSERT_EQ(owl_hmac_generate_key(key2, sizeof(key2)), 0); + + /* key should be non-zero */ + int all_zero = 1; + for (size_t i = 0; i < OWL_HMAC_SHA256_LEN; i++) { + if (key1[i] != 0) { + all_zero = 0; + break; + } + } + ASSERT_EQ(all_zero, 0); + + /* Two generated keys should differ */ + ASSERT_NE(memcmp(key1, key2, OWL_HMAC_SHA256_LEN), 0); +} + +/* ------------------------------------------------------------------------- + * Integrity buffer functions + * ----------------------------------------------------------------------- */ + +TEST(integrity_hmac_baseline_and_check) { + struct owl_integrity ctx; + owl_integrity_init_ctx(&ctx); + + uint8_t buf[256]; + memset(buf, 0x42, sizeof(buf)); + + int ret = owl_integrity_baseline_buffer(&ctx, buf, sizeof(buf)); + ASSERT_EQ(ret, 0); + ASSERT_EQ(ctx.baseline_set, true); + + ret = owl_integrity_check_buffer(&ctx, buf, sizeof(buf)); + ASSERT_EQ(ret, 0); +} + +TEST(integrity_hmac_violation) { + struct owl_integrity ctx; + owl_integrity_init_ctx(&ctx); + + uint8_t buf[256]; + memset(buf, 0x42, sizeof(buf)); + + int ret = owl_integrity_baseline_buffer(&ctx, buf, sizeof(buf)); + ASSERT_EQ(ret, 0); + + /* Modify one byte */ + buf[128] = 0xFF; + ret = owl_integrity_check_buffer(&ctx, buf, sizeof(buf)); + ASSERT_EQ(ret, 1); +} + +/* ------------------------------------------------------------------------- + * Runner + * ----------------------------------------------------------------------- */ + +int main(void) +{ + printf("=== Owlbear HMAC-SHA256 Tests ===\n"); + + RUN_TEST(hmac_rfc4231_test1); + RUN_TEST(hmac_rfc4231_test2); + RUN_TEST(hmac_empty_input); + RUN_TEST(hmac_large_input); + RUN_TEST(hmac_key_change); + RUN_TEST(hmac_same_data_same_hash); + RUN_TEST(hmac_null_inputs); + RUN_TEST(hmac_generate_key); + RUN_TEST(integrity_hmac_baseline_and_check); + RUN_TEST(integrity_hmac_violation); + + TEST_SUMMARY(); + return test_failures; +} diff --git a/tests/test_integrity b/tests/test_integrity index 5c8d098..0847161 100755 Binary files a/tests/test_integrity and b/tests/test_integrity differ diff --git a/tests/test_integrity.c b/tests/test_integrity.c index 037edc0..1526b78 100644 --- a/tests/test_integrity.c +++ b/tests/test_integrity.c @@ -88,11 +88,14 @@ TEST(parse_text_segment_null_input) { TEST(integrity_init_clears_state) { struct owl_integrity ctx; ctx.baseline_set = true; - ctx.baseline_crc = 0xDEADBEEF; + memset(ctx.baseline_hmac, 0xFF, sizeof(ctx.baseline_hmac)); + memset(ctx.hmac_key, 0xFF, sizeof(ctx.hmac_key)); owl_integrity_init_ctx(&ctx); ASSERT_EQ(ctx.baseline_set, false); - ASSERT_EQ(ctx.baseline_crc, 0); + ASSERT_EQ(ctx.baseline_hmac[0], 0); + ASSERT_EQ(ctx.baseline_hmac[31], 0); + ASSERT_EQ(ctx.hmac_key[0], 0); ASSERT_EQ(ctx.target_pid, 0); }