diff --git a/README.md b/README.md index 509783a..aa4a702 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,18 @@ 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 — Private SD-ELEMENT +## This stage — Mutual TLS -RFC 5424 records carry structured data: named groups of key/value pairs a collector parses rather -than pattern-matches out of the message text. Everything used so far has been standard elements, -which mean the same on any device. This stage defines one of our own — which is where a product -says what only it knows, and `app/syslog/SyslogPipelineSd.c` is all it takes to write one. +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. -`logPipeline@32473` reports the protection the record's own path was under: `transport="tls"`, -`atRest="hmac-sha256"`, under our IANA enterprise number so a private name cannot collide with -anyone else's. A collector can confirm that rather than assume it, and alert on a device whose -pipeline has weakened. +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. -**Cost above baseline: Flash +13,548 B, RAM +35,688 B.** +**Cost above baseline: Flash +13,624 B, RAM +37,740 B.** @@ -56,6 +53,7 @@ committed as [`run-report.md`](run-report.md), and rewritten by every stage. | 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 | | 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 | *Deltas are bytes above the baseline, which is itself Flash 350,308 B, RAM 111,192 B.* diff --git a/app/AppConfig.h b/app/AppConfig.h index 1b241d4..ee3b1d5 100644 --- a/app/AppConfig.h +++ b/app/AppConfig.h @@ -30,6 +30,6 @@ * headroom, not spare capacity: buffer_alloc hands out contiguous space, so a * buffer only a little over the peak fails on fragmentation rather than on * capacity. Applied again wherever more is asked of mbedTLS. */ -#define SIMULATED_APP_MBEDTLS_HEAP_BYTES (53 * 1024) +#define SIMULATED_APP_MBEDTLS_HEAP_BYTES (55 * 1024) #endif /* APP_CONFIG_H */ diff --git a/app/syslog/Syslog.c b/app/syslog/Syslog.c index fcc2ab2..fd453fb 100644 --- a/app/syslog/Syslog.c +++ b/app/syslog/Syslog.c @@ -51,7 +51,7 @@ * the resolver numeric-only — no DNS, so no LWIP_DNS and no DNS resolver * component to compile. */ #define SYSLOG_COLLECTOR_HOST "10.0.2.2" -#define SYSLOG_COLLECTOR_PORT ((uint16_t) 6514U) +#define SYSLOG_COLLECTOR_PORT ((uint16_t) 6515U) /* Absorbs records logged while the service task is busy sending. Many devices can * reduce this further: the store holds the backlog, so the ring only has to cover @@ -147,6 +147,11 @@ void Syslog_Start(void) struct SolidSyslogLwipRawTcpStreamConfig tcpConfig = {.Sleep = SyslogSleep}; + /* Both must be set: either one NULL disables mTLS silently, which is why the + * 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(); + /* ServerName must match the name in the collector's certificate. */ struct SolidSyslogMbedTlsStreamConfig tlsConfig = { .Transport = SolidSyslogLwipRawTcpStream_Create(&tcpConfig), @@ -154,6 +159,8 @@ void Syslog_Start(void) .Rng = DeviceCertStore_Rng(), .CaChain = DeviceCertStore_CaChain(), .ServerName = SYSLOG_COLLECTOR_HOST, + .ClientCertChain = clientChain, + .ClientKey = clientKey, }; /* No EndpointVersion — this collector never moves, so the sender resolves @@ -183,7 +190,7 @@ void Syslog_Start(void) .GetIpAt = SyslogOriginIpAt, }; s_sd[2] = SolidSyslogOriginSd_Create(&originConfig); - s_sd[3] = SyslogPipelineSd_Get(); + s_sd[3] = SyslogPipelineSd_Init((clientChain != NULL) && (clientKey != NULL)); struct SolidSyslogMbedTlsHmacSha256PolicyConfig hmacConfig = {.GetKey = SyslogStoreKey}; diff --git a/app/syslog/SyslogPipelineSd.c b/app/syslog/SyslogPipelineSd.c index 8fbbeba..d572503 100644 --- a/app/syslog/SyslogPipelineSd.c +++ b/app/syslog/SyslogPipelineSd.c @@ -8,6 +8,8 @@ #include "SolidSyslogSdValue.h" #include "SolidSyslogStructuredDataDefinition.h" +static const char* s_transport = "tls"; + /* A non-zero enterprise number is what makes the SD-ID private: _Begin emits * "name@number" for one, a bare IANA "name" for 0. */ static void SyslogPipelineSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogSdElement* element) @@ -15,7 +17,7 @@ static void SyslogPipelineSd_Format(struct SolidSyslogStructuredData* base, stru (void) base; SolidSyslogSdElement_Begin(element, "logPipeline", SYSLOG_ENTERPRISE_NUMBER); - SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "transport"), "tls"); + SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "transport"), s_transport); SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "atRest"), "hmac-sha256"); SolidSyslogSdElement_End(element); } @@ -24,7 +26,8 @@ static void SyslogPipelineSd_Format(struct SolidSyslogStructuredData* base, stru * stateless one is a vtable this application owns. */ static struct SolidSyslogStructuredData s_pipelineSd = {SyslogPipelineSd_Format}; -struct SolidSyslogStructuredData* SyslogPipelineSd_Get(void) +struct SolidSyslogStructuredData* SyslogPipelineSd_Init(bool mutualTls) { + s_transport = mutualTls ? "mtls" : "tls"; return &s_pipelineSd; } diff --git a/app/syslog/SyslogPipelineSd.h b/app/syslog/SyslogPipelineSd.h index fb63624..5440b95 100644 --- a/app/syslog/SyslogPipelineSd.h +++ b/app/syslog/SyslogPipelineSd.h @@ -5,9 +5,13 @@ #ifndef APP_SYSLOG_PIPELINE_SD_H #define APP_SYSLOG_PIPELINE_SD_H +#include + struct SolidSyslogStructuredData; -/** The shared instance, for SolidSyslogConfig.Sd. Stateless, so never NULL. */ -struct SolidSyslogStructuredData* SyslogPipelineSd_Get(void); +/** 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); #endif /* APP_SYSLOG_PIPELINE_SD_H */ diff --git a/measurements/mtls.csv b/measurements/mtls.csv new file mode 100644 index 0000000..3ffcc5e --- /dev/null +++ b/measurements/mtls.csv @@ -0,0 +1,13 @@ +# mtls 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,363280 +flash_data,652 +static_bss,148280 +heap_used,4440 +mbedtls_peak,37244 +mbedtls_free,19076 +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 d9db5b2..c58e46e 100644 --- a/measurements/stages.tsv +++ b/measurements/stages.tsv @@ -28,3 +28,4 @@ origin-ip Origin address the device's own address in the record, which a relay o 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 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 diff --git a/run-report.md b/run-report.md index 2653e76..6daa17b 100644 --- a/run-report.md +++ b/run-report.md @@ -1,4 +1,4 @@ -# solid-syslog-example — run (pipeline-sd) +# solid-syslog-example — run (mtls) ## Device (self-measured) @@ -10,14 +10,14 @@ [device] first record logged: yes [report] --- SolidSyslog cost above baseline (simulated existing application) --- [report] key,current,baseline,used_above_baseline -[report] flash_text,363208,349992,13216 -[report] flash_data,648,316,332 -[report] static_bss,146232,110876,35356 +[report] flash_text,363280,349992,13288 +[report] flash_data,652,316,336 +[report] static_bss,148280,110876,37404 [report] heap_used,4440,4440,0 -[report] mbedtls_peak,36096,21332,14764 -[report] mbedtls_free,18176,11436,6740 +[report] mbedtls_peak,37244,21332,15912 +[report] mbedtls_free,19076,11436,7640 [report] lwip_mem_free,7576,7576,0 -[report] lwip_pbufs_free,14,14,0 +[report] lwip_pbufs_free,13,14,-1 [report] stack_log,832,120,712 [report] stack_service,3852,52,3800 [report] stack_harness,2848,2840,8 @@ -29,7 +29,7 @@ ```text text data bss dec hex filename - 363200 656 146232 510088 7c888 /w/build/baseline-cross/baseline.elf + 363272 660 148280 512212 7d0d4 /w/build/baseline-cross/baseline.elf ``` ## Listeners (proved before the device ran) @@ -47,21 +47,21 @@ ## Collector (syslog-ng) received ```text -wire <134>1 2026-07-29T10:57:51.850000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="385"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"][logPipeline@32473 transport="tls" atRest="hmac-sha256"] device started -parsed PRIORITY=134 TIMESTAMP=2026-07-29T10:57:51+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="385"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"][logPipeline@32473 transport="tls" atRest="hmac-sha256"] MSG=device started +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 ``` -## Self-check (vs measurements/pipeline-sd.csv) +## Self-check (vs measurements/mtls.csv) ```text - OK flash_text: 363208 (expected 363208, Δ0) - OK flash_data: 648 (expected 648, Δ0) - OK static_bss: 146232 (expected 146232, Δ0) + OK flash_text: 363280 (expected 363280, Δ0) + OK flash_data: 652 (expected 652, Δ0) + OK static_bss: 148280 (expected 148280, Δ0) OK heap_used: 4440 (expected 4440, Δ0) - OK mbedtls_peak: 36096 (expected 36096, Δ0) - OK mbedtls_free: 18176 (expected 18176, Δ0) + OK mbedtls_peak: 37244 (expected 37244, Δ0) + OK mbedtls_free: 19076 (expected 19076, Δ0) OK lwip_mem_free: 7576 (expected 7576, Δ0) - OK lwip_pbufs_free: 14 (expected 14, Δ0) + OK lwip_pbufs_free: 13 (expected 13, Δ0) OK stack_log: 832 (expected 832, Δ0) OK stack_service: 3852 (expected 3852, Δ0) OK stack_harness: 2848 (expected 2848, Δ0)