Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ 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 — Mutual TLS
## This stage — AES-GCM at rest

The device presents its own certificate, and the collector moves to 6515 — the port that requires
one, and refuses any client that cannot produce it.
Spooled records are encrypted with AES-256-GCM rather than only sealed. Anyone who takes the
volume — a pulled card, a recovered device, a backup — learns nothing from what is on it.

Server-authenticated TLS proves the device is talking to the right collector. Mutual TLS also
proves to the collector which device is talking, so a record is attributable to the holder of a
provisioned key rather than to anything that could reach the port.
The record header is authenticated alongside the encrypted body, so the tamper-evidence of the
previous stage is kept rather than traded away. The key is the same one the seal used: its name
says what it protects, not which algorithm protects it, so strengthening the policy needs nothing
new provisioned.

<!-- STAGE-COST:START (generated by scripts/gen-cost-table.py — do not edit by hand) -->

**Cost above baseline: Flash +13,624 B, RAM +37,740 B.**
**Cost above baseline: Flash +13,780 B, RAM +37,748 B.**

<!-- STAGE-COST:END -->

Expand Down Expand Up @@ -54,6 +55,7 @@ committed as [`run-report.md`](run-report.md), and rewritten by every stage.
| HMAC at rest | stored records that cannot be edited undetected, not merely checked for corruption | +13,424 | +35,684 |
| Private SD-ELEMENT | a record that states the protection its own log pipeline was under | +13,548 | +35,688 |
| Mutual TLS | a collector that knows which device sent the record, not just that one did | +13,624 | +37,740 |
| AES-GCM at rest | spooled records unreadable to anyone holding the disk, not just unforgeable | +13,780 | +37,748 |

*Deltas are bytes above the baseline, which is itself Flash 350,308 B, RAM 111,192 B.*

Expand Down
15 changes: 10 additions & 5 deletions app/syslog/Syslog.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#include "SolidSyslogLwipRawMarshal.h"
#include "SolidSyslogLwipRawResolver.h"
#include "SolidSyslogLwipRawTcpStream.h"
#include "SolidSyslogMbedTlsHmacSha256Policy.h"
#include "SolidSyslogMbedTlsAesGcmPolicy.h"
#include "SolidSyslogMbedTlsStream.h"
#include "SolidSyslogMetaSd.h"
#include "SolidSyslogOriginSd.h"
Expand Down Expand Up @@ -151,12 +151,13 @@ void Syslog_Start(void)
* pipeline element reports what was configured rather than what was intended. */
struct mbedtls_x509_crt* clientChain = DeviceCertStore_ClientChain();
struct mbedtls_pk_context* clientKey = DeviceCertStore_ClientKey();
struct mbedtls_ctr_drbg_context* rng = DeviceCertStore_Rng();

/* ServerName must match the name in the collector's certificate. */
struct SolidSyslogMbedTlsStreamConfig tlsConfig = {
.Transport = SolidSyslogLwipRawTcpStream_Create(&tcpConfig),
.Sleep = SyslogSleep,
.Rng = DeviceCertStore_Rng(),
.Rng = rng,
.CaChain = DeviceCertStore_CaChain(),
.ServerName = SYSLOG_COLLECTOR_HOST,
.ClientCertChain = clientChain,
Expand Down Expand Up @@ -190,17 +191,21 @@ void Syslog_Start(void)
.GetIpAt = SyslogOriginIpAt,
};
s_sd[2] = SolidSyslogOriginSd_Create(&originConfig);
s_sd[3] = SyslogPipelineSd_Init((clientChain != NULL) && (clientKey != NULL));
s_sd[3] = SyslogPipelineSd_Init(
((clientChain != NULL) && (clientKey != NULL)) ? "mtls" : "tls", (rng != NULL) ? "aes-256-gcm" : "none"
);

struct SolidSyslogMbedTlsHmacSha256PolicyConfig hmacConfig = {.GetKey = SyslogStoreKey};
/* The nonce comes from the device's DRBG: GCM needs a fresh one per record and
* mbedTLS has no context-free RNG to reach for. */
struct SolidSyslogMbedTlsAesGcmPolicyConfig gcmConfig = {.GetKey = SyslogStoreKey, .Rng = rng};

/* 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 = SolidSyslogMbedTlsHmacSha256Policy_Create(&hmacConfig),
.SecurityPolicy = SolidSyslogMbedTlsAesGcmPolicy_Create(&gcmConfig),
};

struct SolidSyslogConfig config = {
Expand Down
9 changes: 6 additions & 3 deletions app/syslog/SyslogPipelineSd.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
#include "SolidSyslogSdValue.h"
#include "SolidSyslogStructuredDataDefinition.h"

/* The weakest honest answer, until Init says otherwise. */
static const char* s_transport = "tls";
static const char* s_atRest = "none";

/* A non-zero enterprise number is what makes the SD-ID private: _Begin emits
* "name@number" for one, a bare IANA "name" for 0. */
Expand All @@ -18,16 +20,17 @@ static void SyslogPipelineSd_Format(struct SolidSyslogStructuredData* base, stru

SolidSyslogSdElement_Begin(element, "logPipeline", SYSLOG_ENTERPRISE_NUMBER);
SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "transport"), s_transport);
SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "atRest"), "hmac-sha256");
SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "atRest"), s_atRest);
SolidSyslogSdElement_End(element);
}

/* No _Create and no pool slot: the library never allocates an SD source, so a
* stateless one is a vtable this application owns. */
static struct SolidSyslogStructuredData s_pipelineSd = {SyslogPipelineSd_Format};

struct SolidSyslogStructuredData* SyslogPipelineSd_Init(bool mutualTls)
struct SolidSyslogStructuredData* SyslogPipelineSd_Init(const char* transport, const char* atRest)
{
s_transport = mutualTls ? "mtls" : "tls";
s_transport = transport;
s_atRest = atRest;
return &s_pipelineSd;
}
8 changes: 3 additions & 5 deletions app/syslog/SyslogPipelineSd.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
#ifndef APP_SYSLOG_PIPELINE_SD_H
#define APP_SYSLOG_PIPELINE_SD_H

#include <stdbool.h>

struct SolidSyslogStructuredData;

/** Records what the pipeline was configured with and returns the shared instance,
* for SolidSyslogConfig.Sd. Never NULL. @p mutualTls must reflect the stream
* config, not the intent. */
struct SolidSyslogStructuredData* SyslogPipelineSd_Init(bool mutualTls);
* for SolidSyslogConfig.Sd. Never NULL. Both values must reflect the config, not
* the intent. */
struct SolidSyslogStructuredData* SyslogPipelineSd_Init(const char* transport, const char* atRest);

#endif /* APP_SYSLOG_PIPELINE_SD_H */
13 changes: 13 additions & 0 deletions measurements/aes-gcm.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# aes-gcm 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,363432
flash_data,656
static_bss,148284
heap_used,4440
mbedtls_peak,37152
mbedtls_free,19168
lwip_mem_free,7576
lwip_pbufs_free,13
stack_log,832
stack_service,3852
stack_harness,2848
1 change: 1 addition & 0 deletions measurements/stages.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ tls TLS a collector the device authenticates, and records no longer readable on
hmac HMAC at rest stored records that cannot be edited undetected, not merely checked for corruption
pipeline-sd Private SD-ELEMENT a record that states the protection its own log pipeline was under
mtls Mutual TLS a collector that knows which device sent the record, not just that one did
aes-gcm AES-GCM at rest spooled records unreadable to anyone holding the disk, not just unforgeable
30 changes: 15 additions & 15 deletions run-report.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# solid-syslog-example — run (mtls)
# solid-syslog-example — run (aes-gcm)

## Device (self-measured)

Expand All @@ -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,363280,349992,13288
[report] flash_data,652,316,336
[report] static_bss,148280,110876,37404
[report] flash_text,363432,349992,13440
[report] flash_data,656,316,340
[report] static_bss,148284,110876,37408
[report] heap_used,4440,4440,0
[report] mbedtls_peak,37244,21332,15912
[report] mbedtls_free,19076,11436,7640
[report] mbedtls_peak,37152,21332,15820
[report] mbedtls_free,19168,11436,7732
[report] lwip_mem_free,7576,7576,0
[report] lwip_pbufs_free,13,14,-1
[report] stack_log,832,120,712
Expand All @@ -29,7 +29,7 @@

```text
text data bss dec hex filename
363272 660 148280 512212 7d0d4 /w/build/baseline-cross/baseline.elf
363424 664 148284 512372 7d174 /w/build/baseline-cross/baseline.elf
```

## Listeners (proved before the device ran)
Expand All @@ -47,19 +47,19 @@
## Collector (syslog-ng) received

```text
wire <134>1 2026-07-29T11:06:52.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"][logPipeline@32473 transport="mtls" atRest="hmac-sha256"] device started
parsed PRIORITY=134 TIMESTAMP=2026-07-29T11:06:52+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"][logPipeline@32473 transport="mtls" atRest="hmac-sha256"] MSG=device started
wire <134>1 2026-07-29T11:17:48.360000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="236"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"][logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] device started
parsed PRIORITY=134 TIMESTAMP=2026-07-29T11:17:48+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="236"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"][logPipeline@32473 transport="mtls" atRest="aes-256-gcm"] MSG=device started
```

## Self-check (vs measurements/mtls.csv)
## Self-check (vs measurements/aes-gcm.csv)

```text
OK flash_text: 363280 (expected 363280, Δ0)
OK flash_data: 652 (expected 652, Δ0)
OK static_bss: 148280 (expected 148280, Δ0)
OK flash_text: 363432 (expected 363432, Δ0)
OK flash_data: 656 (expected 656, Δ0)
OK static_bss: 148284 (expected 148284, Δ0)
OK heap_used: 4440 (expected 4440, Δ0)
OK mbedtls_peak: 37244 (expected 37244, Δ0)
OK mbedtls_free: 19076 (expected 19076, Δ0)
OK mbedtls_peak: 37152 (expected 37152, Δ0)
OK mbedtls_free: 19168 (expected 19168, Δ0)
OK lwip_mem_free: 7576 (expected 7576, Δ0)
OK lwip_pbufs_free: 13 (expected 13, Δ0)
OK stack_log: 832 (expected 832, Δ0)
Expand Down
Loading