Skip to content

Mbed TLS

Setup

Platform/MbedTls/ wraps Mbed TLS for TLS transport and keyed at-rest cryptography on embedded targets. It fills the Stream role with TLS and the SecurityPolicy role for at-rest integrity and confidentiality.

What a TLS stream must do is the same whichever library provides it, and is stated once under TLS obligations. This page covers what this adapter needs, how credentials reach it, the coexistence guarantee it makes, and where it does not yet meet that contract.

What it ships

Header What it is
SolidSyslogMbedTlsAesGcmPolicy.h AES-256-GCM confidentiality-and-integrity SecurityPolicy via Mbed TLS, for a store that must encrypt records at rest.
SolidSyslogMbedTlsAesGcmPolicyErrors.h Source identity for the MbedTlsAesGcmPolicy adapter; the detail codes it reports are the portable ones in SolidSyslogAesGcmPolicyErrors.h.
SolidSyslogMbedTlsCredentialsDefinition.h The Mbed TLS credentials vtable (Install / Release) - where a TLS stream's trust anchors, client credential and pinned peer fingerprints come from, and the extension point an implementor fills in.
SolidSyslogMbedTlsHandleCredentials.h An Mbed TLS credentials backend that carries caller-built, caller-owned mbedTLS handles.
SolidSyslogMbedTlsHandleCredentialsErrors.h Source identity for the MbedTlsHandleCredentials adapter; the detail codes it reports are the portable ones in SolidSyslogTlsCredentialsErrors.h.
SolidSyslogMbedTlsHmacSha256Policy.h HMAC-SHA256 integrity SecurityPolicy via Mbed TLS, for a store that must detect tampering of records at rest (authentication only, no confidentiality).
SolidSyslogMbedTlsHmacSha256PolicyErrors.h Source identity for the MbedTlsHmacSha256Policy adapter; the detail codes it reports are the portable ones in SolidSyslogHmacSha256PolicyErrors.h.
SolidSyslogMbedTlsNullCredentials.h The no-op Mbed TLS credentials Null object: installs nothing and releases nothing.
SolidSyslogMbedTlsPemBufferCredentials.h An Mbed TLS credentials backend that parses PEM held in memory, for the duration of one connection.
SolidSyslogMbedTlsPemBufferCredentialsErrors.h Source identity for the MbedTlsPemBufferCredentials adapter; the detail codes it reports are the portable ones in SolidSyslogTlsCredentialsErrors.h.
SolidSyslogMbedTlsStream.h TLS over an injected byte-transport Stream via Mbed TLS, itself a Stream - so a StreamSender speaks TLS to a remote collector without knowing the transport underneath, whether a TCP stream from a platform pack or one the caller supplies.
SolidSyslogMbedTlsStreamErrors.h Source identity for the MbedTlsStream adapter; the detail codes it reports are the portable ones in SolidSyslogTlsStreamErrors.h.

Requirements

The adapter sources compile in your target against your own mbedtls_config.h, so the features you enable are the features it gets.

Mbed TLS 3.6. The pack is tested against 3.6.2 in CI, and its handling of a profile that names no peer has been checked against 3.6.7: from 3.6.3 the library refuses a chain-verified handshake unless the client has said it is deliberately not naming the peer, and the stream says so, so a NULL or empty ServerName behaves as the profile describes on both, the releases either side of that change.

A SolidSyslogSleepFunction is required and has no default.

A seeded mbedtls_ctr_drbg_context is required by the stream and by both credentials sources. Seeded is yours to guarantee: a context that was initialised and never seeded returns bytes without error, makes every session's keys predictable, and cannot be told apart from a seeded one by this adapter.

Credentials come from a credentials source

Where trust anchors, pinned peer fingerprints and the mutual-TLS client credential come from is the integrator's choice rather than this adapter's. The stream is wired to a SolidSyslogMbedTlsCredentials, asked once per connection to install its material on the mbedtls_ssl_config and told once per connection when that material is no longer needed. A source backed by a security element, a PSA opaque key or an encrypted store is a class implementing that role, and needs no change here.

Two sources ship with the pack, and neither opens a file - which is what allows the adapter to run on targets built without MBEDTLS_FS_IO.

SolidSyslogMbedTlsHandleCredentials carries caller-built, caller-owned handles: an mbedtls_x509_crt trust chain, and for mutual TLS an mbedtls_x509_crt and mbedtls_pk_context pair. The integrator parses the material and keeps it parsed.

SolidSyslogMbedTlsPemBufferCredentials carries PEM held in memory and parses it per connection. Between connections nothing but the integrator's own PEM is in memory, which may be read-only flash. What Release frees, Mbed TLS wipes: mbedtls_pk_free zeroises the key context and every limb of the private key, and mbedtls_x509_crt_free zeroises the DER it decoded. This library copies the PEM nowhere, so it holds no second copy to wipe.

The credential window is explicit. Install is called after the transport connects; Release answers every Install, once, after the ssl_config has been freed and with it every pointer into the material. That window is what a source reaching a secure element or an encrypted store needs, and the PEM-buffer source is the worked example of using it.

Because it parses into storage of its own, one PEM-buffer source serves one connection at a time. Wire a second instance for a second stream: a stream opening while another still holds the source is reported and its attempt fails, rather than parsing over material the live connection is using, and its sender connects on a later pass once the first stream has closed.

With the PEM-buffer source, replacing the buffer and moving the stream's configuration version is enough - nothing is freed, and the next connection parses whatever the buffer then points at.

The handle source is different, because rotating it means freeing material the open connection is still reading. Moving the version applies the change but does not say when the old handle stops being read, so the free and the re-parse belong after the connection has closed: either call SolidSyslogSender_Disconnect from the task that services the library and re-parse once it returns, or put the free and the re-parse in a credentials source's Release, which the stream calls when it has finished with the material.

A Release reached by destroying the stream runs inside the configuration lock, because pool cleanup holds it. Freeing and re-parsing your own material there is fine; creating or destroying any SolidSyslog object from Release is not, since that takes the same lock and a non-recursive one deadlocks.

Coexistence is an auditable contract

Platform/MbedTls/Source/ calls no process-global Mbed TLS API. It does not call mbedtls_platform_setup or mbedtls_platform_teardown, install threading-alt hooks, call psa_crypto_init, reset the global random number generator, or replace a debug callback. TLS policy is applied per ssl_config, so it cannot affect the ones you build elsewhere. A device that already uses Mbed TLS for firmware update or a vendor cloud SDK keeps that configuration intact, and the claim can be checked against the directory.

What it reports

Every class in this pack reports portable detail codes, so a handler written against them keeps working if the crypto backend underneath changes. What stays specific to this pack is event->Source, which names the class that reported.

Each role's codes are the whole vocabulary that role can express, so some describe faults this pack cannot have and it never raises them.

From the TLS-stream codes it does not raise CONTEXT_INIT_FAILED. There is no separate context to build here - the configuration is brought up from a library preset, and a failure at that point is DEFAULTS_NOT_APPLIED. Nor CIPHER_POLICY_REJECTED: setting the ciphersuite list cannot fail, so a policy naming only ciphersuites the build lacks surfaces as a refused handshake.

From the credentials codes it does not raise TRUST_ANCHORS_NOT_LOADED. Neither shipped source loads anchors from anywhere: one is handed material the integrator has already parsed, and the other parses a buffer and reports TRUST_ANCHORS_NOT_PARSED if that fails. Which of the two you wire decides what else you can see - the parsing source alone can raise PEM_NOT_TERMINATED, TRUST_ANCHORS_NOT_PARSED and CLIENT_CREDENTIAL_NOT_PARSED, because it is the only one doing the parsing.

The at-rest policies raise every code their roles define.

What a connection is made with

The stream asks for a profile once per connection, and takes the expected peer name, the ciphersuite policy and the certificate profile from it. Nothing is stored between connections, so a change is a matter of returning something different and moving the stream's version.

The ciphersuite policy is one list covering both TLS versions, given as a 0-terminated array of IANA identifiers - the MBEDTLS_TLS_* and MBEDTLS_TLS1_3_* macros. Mbed TLS does not copy it, so the array must stay valid for as long as the connection. Leave it unset and every ciphersuite the build enables is offered, which on a trimmed mbedtls_config.h is whatever was compiled in rather than a curated set; naming a policy is how that becomes a decision rather than a side effect of the build.

The certificate profile is what every certificate in the peer's chain is held to: which signature hashes it may carry, which public-key algorithms and curves, and the minimum RSA key size. Leave it unset and whatever the Mbed TLS you link names as its default profile stands, which is what the section below is about. Mbed TLS does not copy the profile, so it must stay valid for as long as the connection.

Key-exchange groups and the signature algorithms offered in the handshake are not selectable here. TLS 1.3 moved both out of the ciphersuite, so a ciphersuite policy naming a curve has nowhere to go, and a certificate profile covers certificates rather than the handshake.

Each connection is a fresh session with none supplied to resume, and the stream never enables early data or renegotiation, so it meets the contract's resumption and early-data obligations by construction and declines a request from the collector to renegotiate.

A send first reads what the collector has sent, so that a session the collector has closed refuses the record rather than taking it, as the Stream contract requires. A write alone would not notice: Mbed TLS takes the record into the session regardless of the close_notify waiting unread, and the transport beneath may not see the collector's close behind that unread alert. When the read meets the close, or the transport has gone, the send fails, the stream closes itself, and your store replays the record on the next connection. Syslog over TLS carries nothing from collector to device, so the read normally finds nothing; application data it does meet is discarded, not kept for a later read, and the probe makes at most eight reads of up to 16 bytes each, so a close behind more than that goes unnoticed.

Where it falls short of the contract

Certificate validity is checked only where the build carries a clock, below.

An expected identity given as an address literal is matched against an iPAddress entry, as the contract requires, but Mbed TLS also matches it against a DNS name or Common Name spelling the same digits and offers no way to refuse that. A certificate carrying the address only as text therefore passes here. RFC 9525 section 6.2 requires the exact iPAddress match alone.

Of the cryptographic floor, this pack pins the protocol version at TLS 1.2, the finite-field Diffie-Hellman group at 2048 bits, and refuses a peer that will not acknowledge renegotiation_info. What a certificate in the chain may be signed with, and how large its key must be, it does not pin: the X.509 profile in force is whichever one the Mbed TLS you link names as its default, so that part of the floor is a property of that library rather than of this one. Set CertProfile on SolidSyslogMbedTlsProfile and it is stated here instead of inherited.

Every other obligation under TLS obligations is met by this pack as shipped.

Certificate validity depends on your build carrying a clock

MBEDTLS_HAVE_TIME_DATE is what makes Mbed TLS check the dates on a certificate. Without it mbedtls_x509_time_is_past and mbedtls_x509_time_is_future compile to return 0, the expired and not-yet-valid flags are never set, and a certificate outside its validity period is accepted - silently, because nothing failed. On a board with no real-time clock it is tempting to leave the macro off for exactly that reason, and doing so gives up the last time-based control the contract has.

The adapter will not build without it. A target that genuinely has no clock defines SOLIDSYSLOG_MBEDTLS_NO_VALIDITY_CHECK to say so, which builds and leaves the obligation knowingly unmet rather than quietly missing. Nothing reports this at runtime, because by then there is nothing left to detect it with - which is why the decision is forced at build time instead.

Defining it obliges the build to satisfy three separate contracts, and a bare-metal target satisfies each one differently:

Contract Supplied by Where a bare-metal target has to step in
Wall clock, from MBEDTLS_HAVE_TIME mbedtls_time, by default libc time() MBEDTLS_PLATFORM_TIME_ALT, then install a source with mbedtls_platform_set_time. The build will not tell you. A newlib toolchain links time() against a stub that returns -1, and Mbed TLS then reads every certificate as outside its validity period - so a peer that is plainly current is refused as not yet valid
Calendar conversion, from MBEDTLS_HAVE_TIME_DATE mbedtls_platform_gmtime_r, by default gmtime_r or gmtime_s MBEDTLS_PLATFORM_GMTIME_R_ALT, then supply the function - a libc offering neither needs this
Monotonic milliseconds, also from MBEDTLS_HAVE_TIME mbedtls_ms_time MBEDTLS_PLATFORM_MS_TIME_ALT, then supply it from a tick counter - every implementation Mbed TLS ships needs a hosted operating system underneath it, so a bare-metal build has no default to fall back on

Only the first two bear on certificates. The millisecond hook is a separate obligation that comes along with MBEDTLS_HAVE_TIME and has nothing to do with validity; it is listed because the build will not compile without it - the #error is in Mbed TLS's own platform_util.c.

Only the millisecond hook announces itself. A missing calendar conversion fails to compile too, but a missing wall clock does not, which is why it carries the warning above.

A coarse clock is enough. X.509 asks only which side of a window the device is on, so a time fed by SNTP, or seeded at provisioning and advanced by an uptime counter, answers the question a battery-backed RTC would. Time for certificates is also independent of the timestamp a record carries: leaving SolidSyslogConfig.Clock unset still emits NILVALUE in the message.