Integrating SolidSyslog with mbedTLS¶
SolidSyslogMbedTlsStream lets you deliver RFC 5425 (syslog over TLS)
records from SolidSyslog through Mbed TLS instead of OpenSSL. It is the
recommended adapter on embedded / FreeRTOS / bare-metal targets where
OpenSSL is too large or impractical. Hosted Linux / Windows deployments
should use SolidSyslogTlsStream (OpenSSL); both adapters expose the
same SolidSyslogStream vtable, so the rest of the wiring
(SolidSyslogStreamSender, your buffer, your store) is identical.
This document covers what you, the integrator, plug in. It does not re-teach mbedTLS. For that, see the upstream Mbed TLS documentation.
The shape¶
SolidSyslog_Log ─▶ Buffer ─▶ Sender ─▶ SolidSyslogStreamSender
│
▼
SolidSyslogMbedTlsStream ◀── you build CA/cert/key/DRBG handles
│
▼
SolidSyslogStream ◀── you pick / write the TCP backend
│
▼
(your TCP/IP stack)
You supply two things directly: the byte-transport SolidSyslogStream and
the per-context mbedTLS handles passed through
SolidSyslogMbedTlsStreamConfig.
What you need to provide¶
| Item | Owner | Notes |
|---|---|---|
Transport |
You | A SolidSyslogStream* carrying TCP. The library ships SolidSyslogPosixTcpStream (POSIX), SolidSyslogWinsockTcpStream (Windows), and SolidSyslogPlusTcpTcpStream (FreeRTOS-Plus-TCP). If your TCP/IP stack is different (LwIP, NicheStack, vendor BSP), write your own SolidSyslogStream; see Platform/Posix/Source/SolidSyslogPosixTcpStream.c as a reference. |
Sleep |
You | A SolidSyslogSleepFunction. Drives the bounded handshake retry between WANT_READ / WANT_WRITE polls. On FreeRTOS use a vTaskDelay-backed wrapper; on POSIX SolidSyslogPosixSleep is the natural fit. Required. |
GetHandshakeTimeoutMs / HandshakeTimeoutContext |
You (optional) | Per-instance accessor pair for the bounded handshake budget. NULL falls back to the SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS compile-time tunable (default 5000 ms). Install when you need to runtime-tune the handshake deadline: slow peers on a constrained link, or per-tenant policy from your existing configuration store. The accessor is called on every Open. |
Rng |
You | mbedtls_ctr_drbg_context* you seeded yourself. The adapter calls mbedtls_ctr_drbg_random against it. Required. |
CaChain |
You | mbedtls_x509_crt* you parsed yourself (from filesystem, baked-in PEM, HSM, whatever fits your build). Required. |
ServerName |
You | SNI + peer-identity check string. A non-empty name is verified against the server certificate (SAN/CN), rejecting any other CA-issued cert. NULL connects but the peer is unverified (any cert chaining to a trusted CA is accepted, MITM-class), so the library emits a WARNING (BAD_CONFIG / MBEDTLSSTREAM_ERROR_SERVER_NAME_NOT_SET). Note that having no DNS does not force this: an IP-pinned target can still set ServerName to the name (or IP SAN) on its certificate and get full identity verification. Use "" only as a deliberate opt-out for closed networks / private CAs where there is genuinely no name to verify; it connects chain-only with no diagnostic. |
ClientCertChain / ClientKey |
You | mbedtls_x509_crt* + mbedtls_pk_context* for mTLS. Both NULL = server-auth-only TLS. Both non-NULL = mTLS. Supplying only one is treated as "no client cert"; the adapter never half-configures. |
The full struct shape lives in
Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h.
The adapter pins the minimum protocol version to TLS 1.2 on its own
ssl_config rather than inheriting MBEDTLS_SSL_PRESET_DEFAULT, which, on a
permissive mbedTLS build (2.x, or 3.x with MBEDTLS_SSL_PROTO_TLS1_0/1_1
enabled), can otherwise negotiate down to TLS 1.0/1.1. This matches the OpenSSL
reference adapter's explicit floor, so the two are equivalent in downgrade
resistance; it is not something you configure.
Scenario A: you already have Mbed TLS in your image¶
If your firmware already wires Mbed TLS for another subsystem (a cloud
client, an OTA updater, a vendor security framework), you keep that wiring
intact. The adapter consumes the handles you've already built; it never
calls mbedtls_platform_setup / _teardown, never installs
threading-alt hooks, never resets the global RNG, never replaces your
debug callback. See the coexistence contract
below for the auditable list.
Concretely, on top of your existing setup:
- Pick a
SolidSyslogStreamfor the byte transport. Use one of the shipped adapters that matches your TCP/IP stack (SolidSyslogPlusTcpTcpStream,SolidSyslogPosixTcpStream,SolidSyslogWinsockTcpStream) or write your own backing the sameSolidSyslogStreamvtable. If you wrote your own, the existing shipped adapters are the worked examples. - Fill in
SolidSyslogMbedTlsStreamConfigwith the handles you already have:
struct SolidSyslogMbedTlsStreamConfig cfg = {
.Transport = myTcpStream, /* from step 1 */
.Sleep = MyVTaskDelayWrapper, /* or PosixSleep / similar */
.GetHandshakeTimeoutMs = NULL, /* defaults to SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS (5000 ms) */
.HandshakeTimeoutContext = NULL,
.Rng = &myAlreadySeededDrbg,
.CaChain = &myAlreadyParsedCaChain,
.ServerName = "syslog.example.com",
.ClientCertChain = &myClientCert, /* NULL for server-auth-only */
.ClientKey = &myClientKey, /* paired with ClientCertChain */
};
struct SolidSyslogStream* tlsStream = SolidSyslogMbedTlsStream_Create(&cfg);
- Wire
tlsStreaminto aSolidSyslogStreamSenderas theStreamfield, the same way you'd wire a plain TCP stream. RFC 6587 octet-counting framing is applied byStreamSenderon top of the adapter.
That's the whole integration on the SolidSyslog side. There are no
process-wide hooks to install and nothing to teardown beyond the matching
SolidSyslogMbedTlsStream_Destroy when you tear the sender down.
Scenario B: you do not have Mbed TLS yet¶
If you're bringing Mbed TLS in fresh for SolidSyslog, do that work first following the upstream Mbed TLS porting guide. Once Mbed TLS itself is building on your target, you need the following specifically for this adapter:
- A seeded
mbedtls_ctr_drbg_context.mbedtls_entropy_init+mbedtls_entropy_add_sourcefor at least one source registered asMBEDTLS_ENTROPY_SOURCE_STRONG(without a STRONG-tagged source,mbedtls_entropy_funcnever satisfies its internal threshold and everymbedtls_ctr_drbg_seedcall returnsMBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED, silent on the wire, loud in your tests), thenmbedtls_ctr_drbg_init+mbedtls_ctr_drbg_seed. Production-quality entropy is a hardware question: TRNG, vendor HSM, or a board-specific source. psa_crypto_init()called after the DRBG is seeded. Mbed TLS 3.6's TLS 1.3 code path routes through PSA. If PSA isn't initialised, the first handshake state transition returnsMBEDTLS_ERR_ERROR_GENERIC_ERROR(-0x0001) before any TLS bytes leave the socket. If your target has no platform entropy source (a common embedded case),#define MBEDTLS_PSA_CRYPTO_EXTERNAL_RNGin your mbedTLS config and providembedtls_psa_external_get_randomthat wraps the DRBG you just seeded; this keeps PSA and the classic mbedTLS API on the same entropy chain.- A parsed CA chain.
mbedtls_x509_crt_init+mbedtls_x509_crt_parseagainst whatever delivery mechanism fits your build (filesystem on POSIX, baked-in array viaxxd -ion bare-metal, HSM-pulled blob, etc.). PEM input must be NUL-terminated. - (mTLS only) a parsed client cert chain and private key. Same
pattern as the CA chain plus
mbedtls_pk_init/mbedtls_pk_parse_key. - A byte-transport
SolidSyslogStreammatching your TCP/IP stack, exactly as in Scenario A.
A worked end-to-end example for all of the above lives at
Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c
(FreeRTOS-Plus-TCP on QEMU mps2-an385). The matching Mbed TLS config
overrides live at
Bdd/Targets/FreeRtos/mbedtls_user_config.h.
Coexistence contract¶
Platform/MbedTls/Source/ is auditably free of process-global Mbed TLS
calls. The adapter never:
- calls
mbedtls_platform_setup/_teardown - calls
mbedtls_threading_set_alt - calls
psa_crypto_init(you do) - calls
mbedtls_platform_set_calloc_free(you do, if you need it) - calls
mbedtls_debug_set_threshold/mbedtls_ssl_conf_dbg - frees any handle you passed in via the config struct
Everything in that list is global state your existing integration may
already own. Auditors verify the contract by grepping
Platform/MbedTls/Source/; any future change that introduces a global
call must be flagged in review.
FreeRTOS-specific gotchas¶
These bit us during the BDD-target bring-up. If you're on FreeRTOS with newlib, treat them as integrator-side checklist items:
- Route mbedTLS allocations to the RTOS heap. Mbed TLS calls libc
calloc, which on newlib targets typically hits a tiny_sbrk-backed syscall heap (4 KiB in the SolidSyslog BDD reference atBdd/Targets/FreeRtos/Common/Syscalls.c). A singlembedtls_ssl_setupwants ~10–16 KiB and will fail withMBEDTLS_ERR_SSL_ALLOC_FAILED(-0x7F00). SetMBEDTLS_PLATFORM_MEMORYin your config and callmbedtls_platform_set_calloc_free(yourCalloc, yourFree)(pvPortMalloc / vPortFree) before anymbedtls_*_init. - Shrink the TLS record buffers from the 16 KiB default. Set
MBEDTLS_SSL_IN_CONTENT_LENto the largest TLS record your peer will send (server cert + chain is typically 2–4 KiB), andMBEDTLS_SSL_OUT_CONTENT_LENto your largest application message. The defaults cost ~32 KiB of FreeRTOS heap per TLS context. mbedtls_ssl_setupallocates roughlyIN + OUT + ~3 KiBof handshake state. Size your FreeRTOS heap (configTOTAL_HEAP_SIZE) accordingly across all concurrent TLS contexts.MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG+ the external RNG hook are effectively mandatory if you've definedMBEDTLS_NO_PLATFORM_ENTROPY(which you typically have on embedded). Without it,psa_crypto_initreturnsPSA_ERROR_INSUFFICIENT_ENTROPY(-148).
The BDD target's mbedtls_user_config.h shows the minimal config that satisfies the above for QEMU mps2-an385.
Reference integrations¶
| Target | Adapter source | Mbed TLS config | Notes |
|---|---|---|---|
| FreeRTOS QEMU mps2-an385 + FreeRTOS-Plus-TCP | BddTargetTlsSender_MbedTls_PlusTcpTcp.c | mbedtls_user_config.h | Demo-quality entropy and baked-in PEMs; loudly tagged not-for-production. |
| Linux host (host-TDD parity with the embedded path) | Tests/MbedTlsIntegration/ | — | In-process TLS server drives a real handshake against the wrapper. |
| POSIX (OpenSSL reference, for comparison) | BddTargetTlsSender_OpenSsl_PosixTcp.c | — | Same composition shape using SolidSyslogTlsStream for the TLS layer. |
What this adapter does not own¶
- PEM-to-handle conversion: you parse, in whatever way fits your build.
- Certificate rotation: re-parse and rebuild the adapter, or destroy
/ re-create the
SolidSyslogStreamSenderso the next Connect picks up the new chain. - HSM / TRNG integration: your entropy source feeds CTR_DRBG; the adapter consumes the seeded DRBG.
- Per-connection TLS configuration: one adapter instance, one
ssl_config. If you need per-peer cipher / version pinning, build multiple adapters.