A device authentication / access-control gatekeeper: a heimdall-agent
enrolls a device (hardware-derived identity + a real hybrid
Ed25519 + ML-DSA-65 keypair), an administrator approves it, and it
then authenticates to heimdall-server via signed challenge-response
over a small binary protocol. Name: Heimdall, the Norse watchman
guarding the Bifrost, + GK = GateKeeper.
The only accepted security weakness is the admin's PIN-protected recovery key (meant to live on a USB drive, physically held by whoever administers the system) - everything else (a shell on the server, a copy of the database, a stolen agent laptop) is designed to not be a shortcut around it. See docs/threat-model.md for the full reasoning.
- TLS 1.3 on the wire, trust-on-first-use certificate pinning
(
crypto/tls.c) - live-tested handshake, first-contact pinning, and rejection of a changed/MITM certificate. - RBAC with multiple named admins (
AUDITOR/OPERATOR/SUPER_ADMIN,server/src/admin_auth.c, backed by SQLite): admin authority is not "whoever has a shell here" - everyapprove/blockrequires proving possession of a specific, named admin's PIN-protected Ed25519 recovery key. First run walks you through generating the first one - point it at a USB drive, not the server's own disk. - Software HSM (
crypto/hsm.c: PBKDF2-HMAC-SHA256 + AES-256-GCM at rest, key material wiped from memory right after use). A reserved, unimplemented slot exists for a real hardware HSM/PKCS#11 key once hardware is available to test against. - Structured audit logging, SIEM-ready (
server/src/audit.c): every enroll/approve/block/auth event as JSON, optionally forwarded live via RFC 5424 syslog to a SIEM collector (HGK_SYSLOG_HOST). - Optional AD/LDAP bind (
server/src/ldap_auth.c,HGK_LDAP_URI) as a third factor on top of the recovery key - never a replacement for it. - Cryptography: SHA-256 and Ed25519 via OpenSSL, Dilithium/ML-DSA
via liboqs, and a Hybrid mode combining both - not placeholders.
See
tests/test_crypto.c, which also proves tampered signatures and wrong-key signatures are rejected. - Agent identity at rest is encrypted, not a plaintext file:
Windows DPAPI (machine-scoped), Linux AES-256-GCM keyed off
/etc/machine-id(weaker - see the threat model). - Persistence: SQLite (
server/src/database.c), one row per device. - Replay protection: a session's challenge is single-use and
time-limited (
server/src/session.c,tests/test_replay.c). - Hardware fingerprint: real OS-reported identifiers (Windows:
registry
MachineGuid+ computer name + volume serial; Linux:/etc/machine-id+ hostname) - not a hardcoded string, verified deterministic and machine-distinct across both platforms. Bound to every re-authentication, not just enrollment:AUTH_RESPONSEnow carries the current fingerprint andserver/src/verify.cdenies a mismatch, so a cloneddevice_id+private key replayed from different hardware is rejected (tests/test_hardware_binding.c). - TPM: real detection on Windows via the TBS API. Hardware-backed device key generation/signing is not implemented (see docs/threat-model.md); the agent falls back to its software keypair, and says so out loud.
- End-to-end flow: enroll -> admin approval (recovery-key authorized) -> re-authenticate -> access granted, using persisted agent identity, has been run and verified manually - see examples/minimal_enroll_auth.
- No cross-session channel binding for the app-layer challenge - see the threat model for why this matters much less now that TLS prevents network-level replay/MITM.
- No real hardware HSM/PKCS#11 support for admin keys yet
(
HGK_HSM_PKCS11incrypto/hsm.cis a reserved, unimplemented slot - no hardware was available here to test against). - No formal certification (FIPS 140 validated module, SOC 2 audit, Common Criteria) - see docs/compliance.md for what's already aligned versus what needs an accredited lab/auditor.
- No HA/clustering - single server process, local SQLite.
HGK_MSG_REVOKEis declared but unhandled.
Full details in docs/threat-model.md and docs/compliance.md.
Requires a C11 compiler, CMake 3.20+, Ninja, OpenSSL, liboqs, and
SQLite3 from the same toolchain family (mixing runtimes causes
linker errors on Windows/MinGW). On Windows, the recommended source
for all of it is MSYS2's ucrt64
environment:
pacman -S mingw-w64-ucrt-x86_64-gcc \
mingw-w64-ucrt-x86_64-openssl \
mingw-w64-ucrt-x86_64-liboqs \
mingw-w64-ucrt-x86_64-sqlite3 \
mingw-w64-ucrt-x86_64-openldapThen configure and build:
cmake -B build -G Ninja \
-DCMAKE_C_COMPILER=<msys2>/ucrt64/bin/gcc.exe \
-DCMAKE_PREFIX_PATH=<msys2>/ucrt64
cmake --build buildOn Linux, install the equivalent -dev packages for OpenSSL,
liboqs, and SQLite3 through your distro's package manager and drop
-DCMAKE_C_COMPILER/-DCMAKE_PREFIX_PATH.
On Windows, copy the runtime DLLs next to the built executables before running them:
cp <msys2>/ucrt64/bin/{libssl-3-x64,libcrypto-3-x64,liboqs-9,libsqlite3-0,libldap,liblber,libsasl2-3}.dll build/cd build
ctest --output-on-failureTwo harnesses (tests/fuzz/) target the wire-protocol decoder and
the full packet handler. They're built by default
(fuzz_protocol_decode/fuzz_handle_packet next to the other
binaries) but not wired into ctest, since they run millions of
iterations by design:
cd build
./fuzz_protocol_decode # ~3M iterations by default
HGK_FUZZ_ITERATIONS=100000 ./fuzz_handle_packetRun so far without real sanitizers (this project's MinGW/Windows
toolchain has no working ASan/libFuzzer runtime): 5,000,000
iterations of fuzz_protocol_decode and 30,000 of
fuzz_handle_packet, zero crashes. Run again on native Linux
(Ubuntu, clang, real libFuzzer + AddressSanitizer + UBSan this time):
17,245,432 iterations of fuzz_protocol_decode (~142k exec/s)
and 18,289 of fuzz_handle_packet (slower - full enroll/registry/
SQLite path per iteration), zero crashes, zero sanitizer reports
in both. See docs/fuzzing.md for the full
breakdown and exact commands.
See examples/minimal_enroll_auth.
agent/- client: hardware identity, keypair, TLS connection state machine.server/- device registry, SQLite persistence, session/handler, RBAC admin console (admin.c/admin_auth.c), audit logging (audit.c), optional LDAP (ldap_auth.c).common/- the shared wire protocol.crypto/- the crypto backend (heimdall-cryptostatic library): classical/PQC signing (crypto.c,ed25519.c,dilithium.c), the software HSM (hsm.c), TLS (tls.c), CSPRNG (include/random.c).docs/- architecture, protocol spec, threat model, compliance, fuzzing.tests/- unit tests (ctest) and fuzz harnesses (tests/fuzz/).examples/- end-to-end walkthroughs.sdk/- reserved; no third-party client library exists yet, scope not decided.
Found a vulnerability? See SECURITY.md. The security model/tradeoffs are documented in docs/threat-model.md, and enterprise/ compliance readiness in docs/compliance.md.
Current: 1.1.2 (heimdall-agent --version / heimdall-server --version). See CHANGELOG.md.
MIT - see LICENSE.