From 75696794999917b4d494b2080f99b942877cd969 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Wed, 29 Jul 2026 11:51:42 +0100 Subject: [PATCH] feat: seal stored records with HMAC-SHA256 instead of CRC-16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SolidSyslogMbedTlsHmacSha256Policy replaces SolidSyslogCrc16Policy on the store. Records at rest are tamper-evident rather than checksummed: an edit without the key fails verification, and Open compares in constant time. Flash +13,424 B (+348 on the previous stage) RAM +35,684 B (+20) Log stack +712 B (unchanged) Service +3,800 B (unchanged) Twenty bytes, and it is the policy's pool entry — nothing else. The mechanism for holding a named symmetric key and handing it out is the device's own: a device already doing mTLS has provisioned secrets and somewhere to keep them, so the key slot, the loader and the accessor all sit below the line. What SolidSyslog is charged for is the policy and the callback that reaches for the key. No stack movement and no heap: SHA-256 was already linked, and the policy hashes into a caller-owned buffer. The key is fetched per seal and per verify rather than held, so it never sits on the policy instance. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 14 +++++++------- app/syslog/Syslog.c | 15 +++++++++++++-- measurements/hmac.csv | 13 +++++++++++++ measurements/stages.tsv | 1 + run-report.md | 30 +++++++++++++++--------------- 5 files changed, 49 insertions(+), 24 deletions(-) create mode 100644 measurements/hmac.csv diff --git a/README.md b/README.md index 8a52d94..183374d 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,17 @@ It builds on a baseline that simulates the sort of device you might be adding th measures itself: see [docs/baseline.md](docs/baseline.md) for what the baseline is, how the figures are made, and how to run it. -## This stage — TLS +## This stage — HMAC at rest -Records are encrypted in transit, and the collector is authenticated before anything is sent to it. -The sender wraps its TCP stream in mbedTLS, moves to 6514, and checks the collector against the -trust anchor the device already holds. +Stored records are sealed with HMAC-SHA256 instead of a CRC-16. An edit made on the volume no +longer verifies, so the spool becomes tamper-evident rather than merely checked for corruption. -Until now the audit trail crossed the network in cleartext — readable, and alterable, by anything -on the path. +The key is the device's own, fetched from its provisioned key store for each seal and each verify +rather than held by the logger. -**Cost above baseline: Flash +13,076 B, RAM +35,664 B.** +**Cost above baseline: Flash +13,424 B, RAM +35,684 B.** @@ -51,6 +50,7 @@ committed as [`run-report.md`](run-report.md), and rewritten by every stage. | Smaller ring | most of the cap rise given back, now the store rather than the ring holds a backlog | +11,980 | +7,384 | | Origin address | the device's own address in the record, which a relay or NAT between it and the collector cannot rewrite | +12,388 | +7,384 | | TLS | a collector the device authenticates, and records no longer readable on the wire | +13,076 | +35,664 | +| HMAC at rest | stored records that cannot be edited undetected, not merely checked for corruption | +13,424 | +35,684 | *Deltas are bytes above the baseline, which is itself Flash 350,308 B, RAM 111,192 B.* diff --git a/app/syslog/Syslog.c b/app/syslog/Syslog.c index d5d6d0e..65966fd 100644 --- a/app/syslog/Syslog.c +++ b/app/syslog/Syslog.c @@ -14,7 +14,6 @@ #include "SolidSyslogBlockStore.h" #include "SolidSyslogCircularBuffer.h" #include "SolidSyslogConfig.h" -#include "SolidSyslogCrc16Policy.h" #include "SolidSyslogEndpoint.h" #include "SolidSyslogEndpointHost.h" #include "SolidSyslogFatFsFile.h" @@ -25,6 +24,7 @@ #include "SolidSyslogLwipRawMarshal.h" #include "SolidSyslogLwipRawResolver.h" #include "SolidSyslogLwipRawTcpStream.h" +#include "SolidSyslogMbedTlsHmacSha256Policy.h" #include "SolidSyslogMbedTlsStream.h" #include "SolidSyslogMetaSd.h" #include "SolidSyslogOriginSd.h" @@ -60,6 +60,7 @@ /* One "NN.log" per block, on the volume the device already mounts. */ #define SYSLOG_STORE_PREFIX "syslog" #define SYSLOG_STORE_BLOCKS 4U +#define SYSLOG_STORE_KEY_NAME "log-store" #define SYSLOG_SOFTWARE "solid-syslog-example" #define SYSLOG_SW_VERSION "0.1.0" @@ -102,6 +103,14 @@ static void SyslogOriginIpAt(struct SolidSyslogSdValue* value, void* context, si SolidSyslogSdValue_String(value, address); } +/* Fetched per seal and per verify, so the key is never held by the policy. */ +static bool SyslogStoreKey(void* context, uint8_t* keyOut, size_t capacity, size_t* keyLengthOut) +{ + (void) context; + + return DeviceCertStore_SymmetricKey(SYSLOG_STORE_KEY_NAME, keyOut, capacity, keyLengthOut); +} + /* Bounds the connect spin so it yields instead of busy-waiting. */ static void SyslogSleep(int milliseconds) { @@ -174,13 +183,15 @@ void Syslog_Start(void) }; s_sd[2] = SolidSyslogOriginSd_Create(&originConfig); + struct SolidSyslogMbedTlsHmacSha256PolicyConfig hmacConfig = {.GetKey = SyslogStoreKey}; + /* Oldest discarded when the ceiling is reached: a device that cannot reach its * collector should keep the newest evidence, not stop logging. */ struct SolidSyslogBlockStoreConfig storeConfig = { .BlockDevice = SolidSyslogFileBlockDevice_Create(SolidSyslogFatFsFile_Create(), SYSLOG_STORE_PREFIX, 0U), .MaxBlocks = SYSLOG_STORE_BLOCKS, .DiscardPolicy = SOLIDSYSLOG_DISCARD_POLICY_OLDEST, - .SecurityPolicy = SolidSyslogCrc16Policy_Create(), + .SecurityPolicy = SolidSyslogMbedTlsHmacSha256Policy_Create(&hmacConfig), }; struct SolidSyslogConfig config = { diff --git a/measurements/hmac.csv b/measurements/hmac.csv new file mode 100644 index 0000000..9e3d140 --- /dev/null +++ b/measurements/hmac.csv @@ -0,0 +1,13 @@ +# hmac figures (bytes) — captured by scripts/run.sh (CAPTURE=1). +# The device reads measurements/Baseline.csv as its frozen baseline and reports current-minus-Baseline. +flash_text,363088 +flash_data,644 +static_bss,146232 +heap_used,4440 +mbedtls_peak,36096 +mbedtls_free,18176 +lwip_mem_free,7576 +lwip_pbufs_free,13 +stack_log,832 +stack_service,3852 +stack_harness,2848 diff --git a/measurements/stages.tsv b/measurements/stages.tsv index f23a587..fca3243 100644 --- a/measurements/stages.tsv +++ b/measurements/stages.tsv @@ -26,3 +26,4 @@ cap-rise Larger cap headroom for the grown record, so full-width counters cannot buffer-halve Smaller ring most of the cap rise given back, now the store rather than the ring holds a backlog origin-ip Origin address the device's own address in the record, which a relay or NAT between it and the collector cannot rewrite tls TLS a collector the device authenticates, and records no longer readable on the wire +hmac HMAC at rest stored records that cannot be edited undetected, not merely checked for corruption diff --git a/run-report.md b/run-report.md index 813ecdb..5b4ebd0 100644 --- a/run-report.md +++ b/run-report.md @@ -1,4 +1,4 @@ -# solid-syslog-example — run (tls) +# solid-syslog-example — run (hmac) ## Device (self-measured) @@ -10,12 +10,12 @@ [device] first record logged: yes [report] --- SolidSyslog cost above baseline (simulated existing application) --- [report] key,current,baseline,used_above_baseline -[report] flash_text,362736,349992,12744 -[report] flash_data,648,316,332 -[report] static_bss,146208,110876,35332 +[report] flash_text,363088,349992,13096 +[report] flash_data,644,316,328 +[report] static_bss,146232,110876,35356 [report] heap_used,4440,4440,0 -[report] mbedtls_peak,36056,21332,14724 -[report] mbedtls_free,18216,11436,6780 +[report] mbedtls_peak,36096,21332,14764 +[report] mbedtls_free,18176,11436,6740 [report] lwip_mem_free,7576,7576,0 [report] lwip_pbufs_free,13,14,-1 [report] stack_log,832,120,712 @@ -29,7 +29,7 @@ ```text text data bss dec hex filename - 362728 656 146208 509592 7c698 /w/build/baseline-cross/baseline.elf + 363080 652 146232 509964 7c80c /w/build/baseline-cross/baseline.elf ``` ## Listeners (proved before the device ran) @@ -47,19 +47,19 @@ ## Collector (syslog-ng) received ```text -wire <134>1 2026-07-29T10:34:01.430000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="243"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] device started -parsed PRIORITY=134 TIMESTAMP=2026-07-29T10:34:01+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="243"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] MSG=device started +wire <134>1 2026-07-29T10:49:57.430000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="243"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] device started +parsed PRIORITY=134 TIMESTAMP=2026-07-29T10:49:57+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="243"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] MSG=device started ``` -## Self-check (vs measurements/tls.csv) +## Self-check (vs measurements/hmac.csv) ```text - OK flash_text: 362736 (expected 362736, Δ0) - OK flash_data: 648 (expected 648, Δ0) - OK static_bss: 146208 (expected 146208, Δ0) + OK flash_text: 363088 (expected 363088, Δ0) + OK flash_data: 644 (expected 644, Δ0) + OK static_bss: 146232 (expected 146232, Δ0) OK heap_used: 4440 (expected 4440, Δ0) - OK mbedtls_peak: 36056 (expected 36056, Δ0) - OK mbedtls_free: 18216 (expected 18216, Δ0) + OK mbedtls_peak: 36096 (expected 36096, Δ0) + OK mbedtls_free: 18176 (expected 18176, Δ0) OK lwip_mem_free: 7576 (expected 7576, Δ0) OK lwip_pbufs_free: 13 (expected 13, Δ0) OK stack_log: 832 (expected 832, Δ0)