Mbed TLS setup¶
Wiring SolidSyslogMbedTlsStream so a SolidSyslogStreamSender delivers
RFC 5425 syslog over TLS. What any TLS stream must do is under
TLS obligations; what this pack needs and reports is on the
Mbed TLS page. Every config field is documented on its struct. This
page is the order to wire them in, and what bites on the way.
What you need¶
- Mbed TLS built against your own
mbedtls_config.h, withMBEDTLS_HAVE_TIME_DATEon. The adapter will not build without it; the Mbed TLS page says why, and what a target with no clock defines instead. - A seeded
mbedtls_ctr_drbg_context. The stream and both credentials sources take one, and one serves all of them. It must outlive everything built on it. - A platform supplying the TCP stream, the address and the resolver. The capability matrix says which fills each role on your target, and that platform's setup page shows how to create them.
- A
SolidSyslogSleepFunction. The handshake polls and sleeps between polls. Your platform pack supplies one, or wrap your OS sleep in one line.
The adapter sources compile in your target, so add them to your build as adding it to your build describes. Include the Mbed TLS headers before the adapter's, which declare the types by forward reference only:
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/pk.h>
#include <mbedtls/x509_crt.h>
#include "SolidSyslogError.h"
#include "SolidSyslogMbedTlsHandleCredentials.h"
#include "SolidSyslogMbedTlsPemBufferCredentials.h"
#include "SolidSyslogMbedTlsStream.h"
#include "SolidSyslogStreamSender.h"
The layering¶
TLS is a Stream wrapped around another Stream. The TLS stream carries the records; the transport underneath carries the bytes.
StreamSender -> SolidSyslogMbedTlsStream -> your TCP stream -> your TCP/IP stack
|
+-- a credentials source (your CA, certificate and key)
+-- your DRBG
The TLS stream borrows its transport, its credentials and the DRBG. It may close the transport but never destroys any of them.
Before you wire it¶
If Mbed TLS already runs on the target for something else, skip this section: the adapter takes handles you have already built and touches no global state.
Otherwise port Mbed TLS first, following the upstream porting guide. These then fail in ways that are hard to read:
Seed the DRBG from a source registered as MBEDTLS_ENTROPY_SOURCE_STRONG.
Without one, mbedtls_entropy_func never reaches its threshold and every
mbedtls_ctr_drbg_seed returns MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED.
Production entropy is a hardware question: a true random number generator, a
security element, or a board-specific source.
Call psa_crypto_init() after the DRBG is seeded. Mbed TLS 3.6 routes
TLS 1.3 through PSA; uninitialised, the first handshake state transition returns
MBEDTLS_ERR_ERROR_GENERIC_ERROR before any byte reaches the socket.
With MBEDTLS_NO_PLATFORM_ENTROPY defined, give PSA a strong source.
mbedtls_entropy_init registers none of its own, and psa_crypto_init fails
with PSA_ERROR_INSUFFICIENT_ENTROPY. Define MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG
and provide mbedtls_psa_external_get_random wrapping the DRBG you seeded, so
PSA and the classic API run off one chain. A target whose randomness already
arrives through a hardware poll can define MBEDTLS_ENTROPY_HARDWARE_ALT and
provide mbedtls_hardware_poll instead.
1. Install an error handler¶
Every fault below reaches this handler and nothing else. Install it before anything is created.
static void OnError(void* context, const struct SolidSyslogErrorEvent* event)
{
(void) context;
/* event->Source names the class that reported, event->Category the kind
of fault, event->Detail the code. The table at the end of this page
lists what a first connection can produce. */
}
SolidSyslog_SetErrorHandler(OnError, NULL);
2. A credentials source¶
Where the trust anchors, any pinned fingerprints and the client credential come
from. Two sources ship, and neither opens a file, so the adapter runs on targets
built without MBEDTLS_FS_IO.
Handles you have already parsed. Parse the CA chain with
mbedtls_x509_crt_parse, and for mutual TLS the client certificate and
mbedtls_pk_parse_key for its key, from wherever the build keeps them. PEM
input to those parsers must be NUL-terminated, with the length counting the
terminator. The handles are yours and must outlive the credentials.
struct SolidSyslogMbedTlsHandleCredentialsConfig handleConfig = {
.Rng = &drbg,
.CaChain = &caChain,
};
struct SolidSyslogMbedTlsCredentials* credentials =
SolidSyslogMbedTlsHandleCredentials_Create(&handleConfig);
PEM held in memory, parsed once per connection and released when it ends, so between connections nothing but your own PEM is in RAM - which may be flash.
static const unsigned char caPem[] = "-----BEGIN CERTIFICATE-----\n...";
struct SolidSyslogMbedTlsPemBufferCredentialsConfig pemConfig = {
.CaPem = {caPem, sizeof(caPem)},
.Rng = &drbg,
};
struct SolidSyslogMbedTlsCredentials* credentials =
SolidSyslogMbedTlsPemBufferCredentials_Create(&pemConfig);
Each length includes the terminating NUL. A string literal's sizeof does;
an array from xxd -i does not, so give that buffer one byte more and set it. A
length one short is reported rather than left to fail as "not a certificate".
The key must not be encrypted. One PEM-buffer source serves one stream at a
time; wire another for a second stream.
Rng is required on both sources: it checks a client key against its
certificate. Create copies the configuration, so set every field before the
call.
Mutual TLS. Add both halves of the client credential. One without the other is reported and the connection continues server-authenticated, so read the handler rather than assume.
handleConfig.ClientCertChain = &clientCert;
handleConfig.ClientKey = &clientKey;
/* or */
pemConfig.ClientCertPem = {clientCertPem, sizeof(clientCertPem)};
pemConfig.ClientKeyPem = {clientKeyPem, sizeof(clientKeyPem)};
Pinning the collector's certificate, instead of or as well as a CA chain. Both sources take the same two fields:
static const char* const pins[] = {
"sha-256:E1:2D:53:2B:7C:6B:8A:29:A2:76:C8:64:36:0B:08:4B:"
"7A:F1:9E:9D:0C:44:1B:23:5D:87:6E:A0:31:F5:C2:98",
};
handleConfig.PeerFingerprints = pins;
handleConfig.PeerFingerprintCount = 1;
The digest is over the certificate's DER, which is what any certificate tool
prints as its SHA-256 fingerprint; put sha-256: in front of it. Hex digits may
be either case. A pin alone authorises, so CaChain or CaPem may then be
left unset; with both set, both must pass. Across a renewal, list the current
and the next certificate together. Why, and what a sha-1 pin costs, is under
TLS obligations.
3. A profile¶
What a connection is made with - the expected peer identity and the ciphersuite policy - is asked for at each connection through a callback, so a change takes effect on the next connection.
static void FillProfile(struct SolidSyslogMbedTlsProfile* profile, void* context)
{
(void) context;
profile->ServerName = "syslog.example.com";
}
The profile is zeroed before the call, so a field left alone takes the build's
default. ServerName is verified against the certificate and sent as SNI. Left
NULL, the peer is identified by a pin where one is configured; with neither, the
peer is chain-authenticated only and a WARNING says so on every connection. ""
opts out of the name check without the warning. CipherSuites is a
0-terminated array of MBEDTLS_TLS_* and MBEDTLS_TLS1_3_* identifiers that
must stay valid for the connection; leave it alone unless your deployment holds
a policy.
4. The stream¶
struct SolidSyslogMbedTlsStreamConfig tlsConfig = {
.Transport = /* your platform's TCP stream */,
.Sleep = /* your platform's sleep */,
.Rng = &drbg,
.Credentials = credentials,
.Profile = FillProfile,
};
struct SolidSyslogStream* tls = SolidSyslogMbedTlsStream_Create(&tlsConfig);
Transport, Sleep, Rng and Credentials are required. A NULL is reported
at Create and the Null stream is returned, which delivers nothing.
Two optional pairs:
VersionandVersionContext: a function returning a number you increment when the material, the profile or the pin list change. The sender reads it on every record and reconnects when it moves, so a rotation applies without a restart. Leave it NULL if nothing changes at runtime.GetHandshakeTimeoutMsandHandshakeTimeoutContext: the per-attempt handshake deadline. NULL usesSOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS.
5. The sender¶
Unchanged from plain TCP. It sees a Stream and does not know it is a TLS one.
struct SolidSyslogStreamSenderConfig senderConfig = {
.Resolver = resolver, /* your platform's */
.Stream = tls,
.Address = address, /* your platform's */
.Endpoint = GetEndpoint, /* fills the host and port */
};
struct SolidSyslogSender* sender = SolidSyslogStreamSender_Create(&senderConfig);
The expected identity travels with the destination. If Endpoint can return a
different collector at runtime, ServerName has to change with it: move
EndpointVersion and the stream's Version together.
Rotation¶
With the PEM-buffer source, point the config at the new PEM and increment the
stream's version; the next connection parses it. With the handle source the old
handles are still being read by an open connection, so free and re-parse them
only after it has closed: call SolidSyslogSender_Disconnect from the task
that services the library and re-parse when it returns, or do it in a
credentials source's own Release. The Mbed TLS page covers the
lock that Release may run under.
Memory¶
The adapter allocates nothing itself. A session costs what Mbed TLS allocates
under your mbedtls_config.h, dominated by the record buffers, whose defaults
are sized for a host. Budget for every session you run concurrently, from the
upstream sizing guidance.
Where Mbed TLS names an allocation failure in its return, it is reported as
LIBRARY_OUT_OF_MEMORY under the category for the phase it failed in.
Teardown¶
Reverse order: the sender, the address, the TLS stream, the credentials, the transport, then your handles and the DRBG. The stream borrows the credentials and the transport, and the credentials borrow your handles, so each outlives what borrows it.
What the handler sees¶
event->Source is &SolidSyslogMbedTlsStreamErrorSource,
&SolidSyslogMbedTlsHandleCredentialsErrorSource or
&SolidSyslogMbedTlsPemBufferCredentialsErrorSource. event->Detail is a code
from SolidSyslogTlsStreamErrors.h or SolidSyslogTlsCredentialsErrors.h;
the SOLIDSYSLOG_TLS_STREAM_ERROR_ and SOLIDSYSLOG_TLS_CREDENTIALS_ERROR_
prefixes are dropped below. HANDSHAKE_FAILED and INIT_FAILED are
SOLIDSYSLOG_CAT_TLS_STREAM_HANDSHAKE_FAILED and
SOLIDSYSLOG_CAT_TLS_STREAM_INIT_FAILED.
| What happened | Severity | Category | Detail | Then |
|---|---|---|---|---|
Transport, Sleep, Rng or Credentials left NULL |
CRITICAL |
BAD_CONFIG |
NULL_TRANSPORT, NULL_SLEEP, NULL_RNG, NULL_CREDENTIALS |
Null stream returned; nothing delivered |
Rng left NULL on a credentials source |
CRITICAL |
BAD_CONFIG |
NULL_RNG |
Null credentials returned; every connection refused |
mbedtls_ssl_config_defaults or mbedtls_ssl_setup failed |
ERROR |
INIT_FAILED |
DEFAULTS_NOT_APPLIED, SESSION_INIT_FAILED |
refused |
| Mbed TLS reported an allocation failure | ERROR |
the phase's own: INIT_FAILED, BAD_CONFIG or HANDSHAKE_FAILED |
LIBRARY_OUT_OF_MEMORY |
refused. Your Mbed TLS pool, not a fault in the peer - see Memory |
| a PEM length that does not count the NUL | ERROR for the CA, WARNING for the client |
BAD_CONFIG |
PEM_NOT_TERMINATED |
CA: refused. Client: continues without it |
| CA PEM will not parse | ERROR |
BAD_CONFIG |
TRUST_ANCHORS_NOT_PARSED |
refused |
| neither a CA chain nor a pin | ERROR |
BAD_CONFIG |
NO_PEER_AUTHORISATION |
refused |
| a PEM-buffer source already serving another stream | ERROR |
BAD_CONFIG |
ALREADY_IN_USE |
refused until that stream closes |
| a pin not in the RFC 5425 form | ERROR |
BAD_CONFIG |
FINGERPRINT_MALFORMED |
refused |
a pin naming sha-1 |
WARNING |
BAD_CONFIG |
FINGERPRINT_SHA1 |
continues |
no ServerName and no pin |
WARNING |
BAD_CONFIG |
SERVER_NAME_NOT_SET |
continues, chain-authenticated only |
a non-empty ServerName that begins with a dot, or will not install |
ERROR |
BAD_CONFIG |
SERVER_NAME_NOT_APPLIED |
refused |
| half a client credential | WARNING |
BAD_CONFIG |
CLIENT_CREDENTIAL_INCOMPLETE |
continues without it |
| client PEM will not parse | WARNING |
BAD_CONFIG |
CLIENT_CREDENTIAL_NOT_PARSED |
continues without it |
| client key does not match its certificate | WARNING |
BAD_CONFIG |
CLIENT_CREDENTIAL_MISMATCHED |
continues without it |
| collector's chain reaches no anchor | ERROR |
HANDSHAKE_FAILED |
PEER_CERTIFICATE_UNTRUSTED |
refused |
| collector's certificate matches no pin | ERROR |
HANDSHAKE_FAILED |
PEER_FINGERPRINT_MISMATCHED |
refused |
| every pin names a hash the build compiled out | ERROR |
HANDSHAKE_FAILED |
FINGERPRINT_DIGEST_UNAVAILABLE |
refused |
collector's name does not match ServerName |
ERROR |
HANDSHAKE_FAILED |
PEER_NAME_MISMATCHED |
refused |
| collector's certificate outside its dates | ERROR |
HANDSHAKE_FAILED |
PEER_CERTIFICATE_EXPIRED, PEER_CERTIFICATE_NOT_YET_VALID |
refused |
| the handshake failed and no check of ours explains it | ERROR |
HANDSHAKE_FAILED |
HANDSHAKE_REJECTED |
refused. Usually the collector rejecting the device - check the client credential, and the collector's own log |
| handshake did not finish in time | WARNING |
HANDSHAKE_FAILED |
HANDSHAKE_TIMEOUT |
retried |
A refused connection is retried on the sender's next pass. With a store configured, records wait and replay when it succeeds. A successful connection reports nothing.