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
52 changes: 24 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,44 @@ 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

Write a private enterprise SD-ELEMENT. RFC 5424 reserves this form for definitions of your own, and
`SyslogPipelineSd.c` is a complete example of one: it implements the library's structured-data
extension point in its own translation unit.
Add a client certificate and its key to the stream config. The handshake then authenticates the
device to the collector, as well as the collector to the device.

```c
static void SyslogPipelineSd_Format(struct SolidSyslogStructuredData* base, struct SolidSyslogSdElement* element)
{
(void) base;
struct SolidSyslogMbedTlsStreamConfig tlsConfig = {
/* ... as the previous TLS stage ... */
.ClientCertChain = DeviceCertStore_ClientChain(),
.ClientKey = DeviceCertStore_ClientKey(),
};
```

SolidSyslogSdElement_Begin(element, "logPipeline", SYSLOG_ENTERPRISE_NUMBER);
SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "transport"), "tls");
SolidSyslogSdValue_String(SolidSyslogSdElement_Param(element, "atRest"), "hmac-sha256");
SolidSyslogSdElement_End(element);
}
Both fields must be set. Supplying one and not the other leaves the connection
server-authenticated and does not fail, so the pipeline element is given what the device holds
rather than what was configured:

static struct SolidSyslogStructuredData s_pipelineSd = {SyslogPipelineSd_Format};
```c
s_sd[3] = SyslogPipelineSd_Init((clientChain != NULL) && (clientKey != NULL));
```

```text
... [logPipeline@32473 transport="tls" atRest="hmac-sha256"] device started
... [logPipeline@32473 transport="mtls" atRest="hmac-sha256"] device started
```

The vtable has one entry, `Format`, and the library never allocates the object. A stateless source
therefore needs no `_Create` and no pool slot; it is a static this application owns and points the
config at. A source with per-instance state puts that state alongside the vtable in the same struct
and reads it back from the `base` parameter.

A non-zero enterprise number is what produces a private SD-ID: `_Begin` emits `name@number` for one
and a bare IANA `name` for zero. `SyslogEnterprise.h` now defines the number and derives the string
that `origin`'s `enterpriseId` carries, so the two forms cannot drift.
The handshake authenticates the TLS peer. Where a relay, gateway or broker terminates the
connection, the collector authenticates that hop rather than the device behind it, and the `origin`
element carries the device's own identity across it.

What the element reports is the state of the logging path. A collector can confirm that a record
arrived over TLS and was sealed at rest, and can alert on a device whose pipeline has weakened.
The remaining stages change both values as the protection changes.
The collector port used here requires a client certificate and refuses a client that presents none.

**When you need it.** If a collector has to verify the protection a record travelled and rested
under rather than assume it.
**When you need it.** When the receiver has to authenticate the device rather than accept the
identity the record claims. It requires a certificate per device, protected storage for the private
key, and an issuing and revocation process behind both.

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

**Cost above baseline: Flash +13,556 B, RAM +37,476 B.**
**Cost above baseline: Flash +13,632 B, RAM +39,528 B.**

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

Expand Down Expand Up @@ -81,6 +76,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,084 | +37,452 |
| HMAC at rest | stored records that cannot be edited undetected, not merely checked for corruption | +13,416 | +37,472 |
| Private SD-ELEMENT | a record that states the protection its own log pipeline was under | +13,556 | +37,476 |
| Mutual TLS | a collector that knows which device sent the record, not just that one did | +13,632 | +39,528 |

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

Expand Down
2 changes: 1 addition & 1 deletion app/AppConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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 */
11 changes: 9 additions & 2 deletions app/syslog/Syslog.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,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)

/* Depth enough to absorb a burst while the sender is busy, without sizing for a
* backlog the store is there to hold. */
Expand Down Expand Up @@ -148,13 +148,20 @@ 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 the device holds rather than what was meant. */
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),
.Sleep = SyslogSleep,
.Rng = DeviceCertStore_Rng(),
.CaChain = DeviceCertStore_CaChain(),
.ServerName = SYSLOG_COLLECTOR_HOST,
.ClientCertChain = clientChain,
.ClientKey = clientKey,
};

/* No EndpointVersion — this collector never moves, so the sender resolves
Expand Down Expand Up @@ -184,7 +191,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};

Expand Down
11 changes: 7 additions & 4 deletions app/syslog/SyslogPipelineSd.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,26 @@
#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)
{
(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);
}

/* No _Create and no pool slot: the library never allocates an SD source, so a
* stateless one is a vtable this application owns. */
/* No _Create and no pool slot: the library never allocates an SD source, so this
* one is a vtable the 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;
}
7 changes: 5 additions & 2 deletions app/syslog/SyslogPipelineSd.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
#ifndef APP_SYSLOG_PIPELINE_SD_H
#define APP_SYSLOG_PIPELINE_SD_H

#include <stdbool.h>

struct SolidSyslogStructuredData;

/** The shared instance, for SolidSyslogConfig.Sd. Stateless, so never NULL. */
struct SolidSyslogStructuredData* SyslogPipelineSd_Get(void);
/** The shared instance, for SolidSyslogConfig.Sd. Never NULL. @p mutualTls is
* what the device holds, not what it meant to configure. */
struct SolidSyslogStructuredData* SyslogPipelineSd_Init(bool mutualTls);

#endif /* APP_SYSLOG_PIPELINE_SD_H */
13 changes: 13 additions & 0 deletions measurements/mtls.csv
Original file line number Diff line number Diff line change
@@ -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,363288
flash_data,652
static_bss,150068
heap_used,4440
mbedtls_peak,37200
mbedtls_free,19120
lwip_mem_free,7576
lwip_pbufs_free,13
stack_log,800
stack_service,3820
stack_harness,2848
1 change: 1 addition & 0 deletions measurements/stages.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,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
34 changes: 17 additions & 17 deletions run-report.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# solid-syslog-example — run (pipeline-sd)
# solid-syslog-example — run (mtls)

## Device (self-measured)

Expand All @@ -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,363216,349992,13224
[report] flash_data,648,316,332
[report] static_bss,148020,110876,37144
[report] flash_text,363288,349992,13296
[report] flash_data,652,316,336
[report] static_bss,150068,110876,39192
[report] heap_used,4440,4440,0
[report] mbedtls_peak,36080,21332,14748
[report] mbedtls_free,18192,11436,6756
[report] mbedtls_peak,37244,21332,15912
[report] mbedtls_free,19076,11436,7640
[report] lwip_mem_free,7576,7576,0
[report] lwip_pbufs_free,13,14,-1
[report] lwip_pbufs_free,14,14,0
[report] stack_log,800,120,680
[report] stack_service,3820,52,3768
[report] stack_harness,2848,2840,8
Expand All @@ -29,7 +29,7 @@

```text
text data bss dec hex filename
363208 656 148020 511884 7cf8c /w/build/baseline-cross/baseline.elf
363280 660 150068 514008 7d7d8 /w/build/baseline-cross/baseline.elf
```

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

```text
wire <134>1 2026-08-16T11:35:22.510000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="251"][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-08-16T11:35:22+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="251"][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-08-16T11:45:19.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="mtls" atRest="hmac-sha256"] device started
parsed PRIORITY=134 TIMESTAMP=2026-08-16T11:45:19+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="mtls" atRest="hmac-sha256"] MSG=device started
```

## Self-check (vs measurements/pipeline-sd.csv)
## Self-check (vs measurements/mtls.csv)

```text
OK flash_text: 363216 (expected 363216, Δ0)
OK flash_data: 648 (expected 648, Δ0)
OK static_bss: 148020 (expected 148020, Δ0)
OK flash_text: 363288 (expected 363288, Δ0)
OK flash_data: 652 (expected 652, Δ0)
OK static_bss: 150068 (expected 150068, Δ0)
OK heap_used: 4440 (expected 4440, Δ0)
OK mbedtls_peak: 36080 (expected 36104, Δ24)
OK mbedtls_free: 18192 (expected 18168, Δ24)
OK mbedtls_peak: 37244 (expected 37200, Δ44)
OK mbedtls_free: 19076 (expected 19120, Δ44)
OK lwip_mem_free: 7576 (expected 7576, Δ0)
OK lwip_pbufs_free: 13 (expected 14, Δ1)
OK lwip_pbufs_free: 14 (expected 13, Δ1)
OK stack_log: 800 (expected 800, Δ0)
OK stack_service: 3820 (expected 3820, Δ0)
OK stack_harness: 2848 (expected 2848, Δ0)
Expand Down
Loading