From 24c9da628ea8c298b1ffdf507e635fe610244c2b Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 1 Sep 2026 21:30:49 +0100 Subject: [PATCH 1/4] feat: an Mbed TLS credentials backend that parses PEM per connection --- Core/Interface/SolidSyslogTunablesDefaults.h | 7 +- Platform/MbedTls/CMakeLists.txt | 2 + .../SolidSyslogMbedTlsPemBufferCredentials.h | 82 ++++ ...dSyslogMbedTlsPemBufferCredentialsErrors.h | 43 ++ .../SolidSyslogMbedTlsPemBufferCredentials.c | 275 ++++++++++++ ...SyslogMbedTlsPemBufferCredentialsPrivate.h | 46 ++ ...dSyslogMbedTlsPemBufferCredentialsStatic.c | 129 ++++++ Tests/MbedTlsIntegration/CMakeLists.txt | 6 + Tests/MbedTlsIntegration/MbedTlsTestCert.c | 28 ++ Tests/MbedTlsIntegration/MbedTlsTestCert.h | 8 + ...dSyslogMbedTlsPemBufferCredentialsTest.cpp | 394 ++++++++++++++++++ docs/generated/MbedTls-manifest.txt | 2 + docs/generated/beta-stack-manifest.txt | 2 + docs/platforms/mbedtls/index.md | 33 +- docs/platforms/mbedtls/setup.md | 33 +- misra_suppressions.txt | 1 + 16 files changed, 1072 insertions(+), 19 deletions(-) create mode 100644 Platform/MbedTls/Interface/SolidSyslogMbedTlsPemBufferCredentials.h create mode 100644 Platform/MbedTls/Interface/SolidSyslogMbedTlsPemBufferCredentialsErrors.h create mode 100644 Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c create mode 100644 Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentialsPrivate.h create mode 100644 Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentialsStatic.c create mode 100644 Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index 867c90d0..a01b1217 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -594,9 +594,10 @@ /** * Role pool: TLS credentials. Number of credentials instances the library's * internal static pool can simultaneously hold, across whichever backend is - * compiled in - a PEM-file source, caller-built vendor handles, or an - * integrator's own class reaching a secure element or key store. Each - * instance carries only where its material comes from, never the material. + * compiled in - a PEM source, caller-built vendor handles, or an + * integrator's own class reaching a secure element or key store. An instance + * carries where its material comes from; a backend that acquires material per + * connection also holds what it acquired, for the length of that connection. * * Default 1 - one source per TLS stream is the ordinary wiring, and the * stream pool defaults to one. Bump it alongside diff --git a/Platform/MbedTls/CMakeLists.txt b/Platform/MbedTls/CMakeLists.txt index 0ff65143..42855761 100644 --- a/Platform/MbedTls/CMakeLists.txt +++ b/Platform/MbedTls/CMakeLists.txt @@ -13,6 +13,8 @@ add_library(SolidSyslogMbedTls INTERFACE) target_sources(SolidSyslogMbedTls INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsNullCredentials.c ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsHandleCredentials.c + ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsPemBufferCredentials.c + ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsPemBufferCredentialsStatic.c ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsStream.c ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsStreamStatic.c diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsPemBufferCredentials.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsPemBufferCredentials.h new file mode 100644 index 00000000..995855f0 --- /dev/null +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsPemBufferCredentials.h @@ -0,0 +1,82 @@ +/* SPDX-FileCopyrightText: Copyright 2026 Cozens Software Solutions Limited + * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial + */ + +/** @file + * An Mbed TLS credentials backend that parses PEM held in memory, for the + * duration of one connection. + * + * Where the handle backend asks the integrator to keep parsed material alive, + * this one parses on Install and releases on Close, so between connections + * nothing but the integrator's own PEM is in memory. The PEM may live in + * read-only flash, or be fetched into a buffer the integrator wipes itself; + * this library copies none of it. + * + * 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. */ +#ifndef SOLIDSYSLOGMBEDTLSPEMBUFFERCREDENTIALS_H +#define SOLIDSYSLOGMBEDTLSPEMBUFFERCREDENTIALS_H + +#include + +#include "SolidSyslogExternC.h" + +/* Forward declarations keep the header free of any mbedTLS include, as the + * stream header does. Integrators include the relevant mbedTLS headers + * themselves before this one to bring the types into scope. */ +struct mbedtls_ctr_drbg_context; + +SOLIDSYSLOG_EXTERN_C_BEGIN + + struct SolidSyslogMbedTlsCredentials; + + /** PEM text and its extent. Borrowed - the caller owns the bytes and must + * keep them valid for the lifetime of the credentials. */ + struct SolidSyslogMbedTlsPemBuffer + { + /** PEM text; NULL means this piece of material is not supplied. */ + const unsigned char* Bytes; + /** Length of Bytes **including** the terminating NUL, which is the + * length Mbed TLS's own parsers require of PEM - so + * `strlen(pem) + 1`. Getting this wrong is reported rather than left + * to surface as a parse failure: Mbed TLS reads a certificate buffer + * whose last byte is not NUL as DER instead, which fails as "not a + * certificate" and says nothing about the length. */ + size_t Length; + }; + + /** Where this backend's material lives. */ + struct SolidSyslogMbedTlsPemBufferCredentialsConfig + { + /** Trust anchors the peer certificate must chain to; an unsupplied + * buffer installs none, which leaves the peer authorised only if the + * stream has another means to do it. */ + struct SolidSyslogMbedTlsPemBuffer CaPem; + /** Leaf certificate (plus intermediates) for mutual TLS. Certificate + * and key are all-or-nothing - supplying one without the other is + * reported. */ + struct SolidSyslogMbedTlsPemBuffer ClientCertPem; + /** Private key matching ClientCertPem. Must not be encrypted: no + * password can be supplied. */ + struct SolidSyslogMbedTlsPemBuffer ClientKeyPem; + /** Seeded CTR-DRBG. Mbed TLS requires one to parse a private key, and + * it also checks the key against its certificate; required - a NULL is + * reported at SolidSyslogMbedTlsPemBufferCredentials_Create. The + * stream takes its own handshake RNG separately, and the same one + * serves both. */ + struct mbedtls_ctr_drbg_context* Rng; + }; + + /** Draw a credentials instance from the pool. A NULL config or a NULL Rng is + * reported and falls back to the shared Null credentials, as does an + * exhausted pool. */ + struct SolidSyslogMbedTlsCredentials* SolidSyslogMbedTlsPemBufferCredentials_Create( + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config + ); + /** Release the pool slot, freeing any material still parsed into it. */ + void SolidSyslogMbedTlsPemBufferCredentials_Destroy(struct SolidSyslogMbedTlsCredentials * base); + +SOLIDSYSLOG_EXTERN_C_END + +#endif /* SOLIDSYSLOGMBEDTLSPEMBUFFERCREDENTIALS_H */ diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsPemBufferCredentialsErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsPemBufferCredentialsErrors.h new file mode 100644 index 00000000..53c79e36 --- /dev/null +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsPemBufferCredentialsErrors.h @@ -0,0 +1,43 @@ +/* SPDX-FileCopyrightText: Copyright 2026 Cozens Software Solutions Limited + * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial + */ + +/** @file + * Error codes and Source identity for the MbedTlsPemBufferCredentials backend. */ +#ifndef SOLIDSYSLOGMBEDTLSPEMBUFFERCREDENTIALSERRORS_H +#define SOLIDSYSLOGMBEDTLSPEMBUFFERCREDENTIALSERRORS_H + +#include "SolidSyslogExternC.h" + +SOLIDSYSLOG_EXTERN_C_BEGIN + + struct SolidSyslogErrorSource; + + /** Detail codes for events whose Source is SolidSyslogMbedTlsPemBufferCredentialsErrorSource. + * A handler reads these off event->Detail after matching event->Source; the + * members name their own fault. */ + enum SolidSyslogMbedTlsPemBufferCredentialsErrors + { + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_POOL_EXHAUSTED, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_UNKNOWN_DESTROY, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_NULL_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_NULL_RNG, + /** A buffer's Length does not include a terminating NUL. */ + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_PEM_NOT_TERMINATED, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_TRUST_ANCHORS_NOT_PARSED, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_INCOMPLETE, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_NOT_PARSED, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_MISMATCHED, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_NOT_INSTALLED, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_MAX /**< One past the last code; never emitted. Bounds the range for iteration. */ + }; + + /** Identity for events raised by an MbedTlsPemBufferCredentials. A handler + * matches by address (event->Source == &SolidSyslogMbedTlsPemBufferCredentialsErrorSource), + * then reads event->Detail as an enum + * SolidSyslogMbedTlsPemBufferCredentialsErrors. */ + extern const struct SolidSyslogErrorSource SolidSyslogMbedTlsPemBufferCredentialsErrorSource; + +SOLIDSYSLOG_EXTERN_C_END + +#endif /* SOLIDSYSLOGMBEDTLSPEMBUFFERCREDENTIALSERRORS_H */ diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c new file mode 100644 index 00000000..288c3937 --- /dev/null +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c @@ -0,0 +1,275 @@ +/* SPDX-FileCopyrightText: Copyright 2026 Cozens Software Solutions Limited + * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial + */ + +#include "SolidSyslogMbedTlsPemBufferCredentials.h" + +#include +#include +#include +#include +#include +#include + +#include "SolidSyslogErrorCategory.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" +#include "SolidSyslogMbedTlsPemBufferCredentialsPrivate.h" +#include "SolidSyslogTlsCredentialsInstalled.h" + +const struct SolidSyslogErrorSource SolidSyslogMbedTlsPemBufferCredentialsErrorSource = {"MbedTlsPemBufferCredentials"}; + +static bool MbedTlsPemBufferCredentials_Install( + struct SolidSyslogMbedTlsCredentials* base, + struct mbedtls_ssl_config* conf, + struct SolidSyslogTlsCredentialsInstalled* installed +); +static inline bool MbedTlsPemBufferCredentials_ParseTrustAnchors( + struct SolidSyslogMbedTlsPemBufferCredentials* self, + struct mbedtls_ssl_config* conf +); +static inline void MbedTlsPemBufferCredentials_ConfigureClientIdentity( + struct SolidSyslogMbedTlsPemBufferCredentials* self, + struct mbedtls_ssl_config* conf +); +static inline bool MbedTlsPemBufferCredentials_HasClientCredential( + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config +); +static inline bool MbedTlsPemBufferCredentials_HasHalfOfClientCredential( + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config +); +static inline bool MbedTlsPemBufferCredentials_ParseClientCredential(struct SolidSyslogMbedTlsPemBufferCredentials* self +); +static inline bool MbedTlsPemBufferCredentials_ClientKeyMatchesCertificate( + struct SolidSyslogMbedTlsPemBufferCredentials* self +); +static inline bool MbedTlsPemBufferCredentials_IsSupplied(const struct SolidSyslogMbedTlsPemBuffer* pem); +static inline bool MbedTlsPemBufferCredentials_IsTerminated(const struct SolidSyslogMbedTlsPemBuffer* pem); +static void MbedTlsPemBufferCredentials_Release(struct SolidSyslogMbedTlsCredentials* base); +static inline struct SolidSyslogMbedTlsPemBufferCredentials* MbedTlsPemBufferCredentials_SelfFromBase( + struct SolidSyslogMbedTlsCredentials* base +); + +void SolidSyslogMbedTlsPemBufferCredentials_Initialise( + struct SolidSyslogMbedTlsCredentials* base, + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config +) +{ + struct SolidSyslogMbedTlsPemBufferCredentials* self = MbedTlsPemBufferCredentials_SelfFromBase(base); + self->Base.Install = MbedTlsPemBufferCredentials_Install; + self->Base.Release = MbedTlsPemBufferCredentials_Release; + self->Config = *config; + mbedtls_x509_crt_init(&self->CaChain); + mbedtls_x509_crt_init(&self->ClientCertChain); + mbedtls_pk_init(&self->ClientKey); +} + +static inline struct SolidSyslogMbedTlsPemBufferCredentials* MbedTlsPemBufferCredentials_SelfFromBase( + struct SolidSyslogMbedTlsCredentials* base +) +{ + return (struct SolidSyslogMbedTlsPemBufferCredentials*) base; +} + +void SolidSyslogMbedTlsPemBufferCredentials_Cleanup(struct SolidSyslogMbedTlsCredentials* base) +{ + MbedTlsPemBufferCredentials_Release(base); +} + +/* Parsed here rather than at Create, so the material exists only for a + * connection actually being made. Every Install is answered by a Release, which + * is where it goes again. */ +static bool MbedTlsPemBufferCredentials_Install( + struct SolidSyslogMbedTlsCredentials* base, + struct mbedtls_ssl_config* conf, + struct SolidSyslogTlsCredentialsInstalled* installed +) +{ + struct SolidSyslogMbedTlsPemBufferCredentials* self = MbedTlsPemBufferCredentials_SelfFromBase(base); + installed->TrustAnchorsInstalled = false; + installed->Fingerprints = NULL; + installed->FingerprintCount = 0U; + bool ok = true; + if (MbedTlsPemBufferCredentials_IsSupplied(&self->Config.CaPem)) + { + installed->TrustAnchorsInstalled = MbedTlsPemBufferCredentials_ParseTrustAnchors(self, conf); + ok = installed->TrustAnchorsInstalled; + } + if (ok) + { + MbedTlsPemBufferCredentials_ConfigureClientIdentity(self, conf); + } + return ok; +} + +/* No fault in our own credential stops delivery: the collector is the + * enforcement point for it, and one that requires a client certificate refuses + * the handshake anyway. Every failure here leaves nothing installed, so the + * connection continues server-authenticated rather than half-presenting a + * credential. Whatever was parsed before the fault is released with the rest at + * Close. */ +static inline void MbedTlsPemBufferCredentials_ConfigureClientIdentity( + struct SolidSyslogMbedTlsPemBufferCredentials* self, + struct mbedtls_ssl_config* conf +) +{ + if (MbedTlsPemBufferCredentials_HasClientCredential(&self->Config)) + { + if (MbedTlsPemBufferCredentials_ParseClientCredential(self) == false) + { + MbedTlsPemBufferCredentials_Report( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_NOT_PARSED + ); + } + else if (MbedTlsPemBufferCredentials_ClientKeyMatchesCertificate(self) == false) + { + MbedTlsPemBufferCredentials_Report( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_MISMATCHED + ); + } + /* Only MBEDTLS_ERR_SSL_ALLOC_FAILED, which returns before the key_cert + * node is appended, so nothing is left half-configured. */ + else if (mbedtls_ssl_conf_own_cert(conf, &self->ClientCertChain, &self->ClientKey) != 0) + { + MbedTlsPemBufferCredentials_Report( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_NOT_INSTALLED + ); + } + else + { + /* Parsed, paired and installed - the credential will be presented. */ + } + } + else if (MbedTlsPemBufferCredentials_HasHalfOfClientCredential(&self->Config)) + { + MbedTlsPemBufferCredentials_Report( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_INCOMPLETE + ); + } + else + { + /* Neither supplied - server-authenticated TLS is the deliberate case. */ + } +} + +static inline bool MbedTlsPemBufferCredentials_HasClientCredential( + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config +) +{ + return MbedTlsPemBufferCredentials_IsSupplied(&config->ClientCertPem) && + MbedTlsPemBufferCredentials_IsSupplied(&config->ClientKeyPem); +} + +/* One half without the other. The integrator asked for mutual TLS and will not + * get it, so it is reported rather than read as a decision to go without. */ +static inline bool MbedTlsPemBufferCredentials_HasHalfOfClientCredential( + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config +) +{ + return MbedTlsPemBufferCredentials_IsSupplied(&config->ClientCertPem) != + MbedTlsPemBufferCredentials_IsSupplied(&config->ClientKeyPem); +} + +/* Both halves in one place: a certificate without its key is no more use than + * neither, so an unterminated or unparseable half fails the pair. */ +static inline bool MbedTlsPemBufferCredentials_ParseClientCredential(struct SolidSyslogMbedTlsPemBufferCredentials* self +) +{ + bool parsed = false; + if (MbedTlsPemBufferCredentials_IsTerminated(&self->Config.ClientCertPem) && + MbedTlsPemBufferCredentials_IsTerminated(&self->Config.ClientKeyPem)) + { + parsed = (mbedtls_x509_crt_parse( + &self->ClientCertChain, + self->Config.ClientCertPem.Bytes, + self->Config.ClientCertPem.Length + ) == 0) && + (mbedtls_pk_parse_key( + &self->ClientKey, + self->Config.ClientKeyPem.Bytes, + self->Config.ClientKeyPem.Length, + NULL, + 0U, + mbedtls_ctr_drbg_random, + self->Config.Rng + ) == 0); + } + return parsed; +} + +/* mbedtls_ssl_conf_own_cert does not check the pair it is handed, and names this + * function in its own documentation as the way to check it. */ +static inline bool MbedTlsPemBufferCredentials_ClientKeyMatchesCertificate( + struct SolidSyslogMbedTlsPemBufferCredentials* self +) +{ + return mbedtls_pk_check_pair( + &self->ClientCertChain.pk, + &self->ClientKey, + mbedtls_ctr_drbg_random, + self->Config.Rng + ) == 0; +} + +/* Mbed TLS reads a certificate buffer whose last byte is not NUL as DER, so a + * length given as strlen rather than strlen + 1 fails as "not a certificate" + * and says nothing about the length. The check is one byte inside the declared + * extent, and it names the fault the integrator actually made. */ +static inline bool MbedTlsPemBufferCredentials_ParseTrustAnchors( + struct SolidSyslogMbedTlsPemBufferCredentials* self, + struct mbedtls_ssl_config* conf +) +{ + bool parsed = false; + if (MbedTlsPemBufferCredentials_IsTerminated(&self->Config.CaPem) == false) + { + MbedTlsPemBufferCredentials_Report( + SOLIDSYSLOG_SEVERITY_ERROR, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_PEM_NOT_TERMINATED + ); + } + else if (mbedtls_x509_crt_parse(&self->CaChain, self->Config.CaPem.Bytes, self->Config.CaPem.Length) != 0) + { + MbedTlsPemBufferCredentials_Report( + SOLIDSYSLOG_SEVERITY_ERROR, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_TRUST_ANCHORS_NOT_PARSED + ); + } + else + { + mbedtls_ssl_conf_ca_chain(conf, &self->CaChain, NULL); + parsed = true; + } + return parsed; +} + +static inline bool MbedTlsPemBufferCredentials_IsSupplied(const struct SolidSyslogMbedTlsPemBuffer* pem) +{ + return (pem->Bytes != NULL) && (pem->Length > 0U); +} + +static inline bool MbedTlsPemBufferCredentials_IsTerminated(const struct SolidSyslogMbedTlsPemBuffer* pem) +{ + return pem->Bytes[pem->Length - 1U] == (unsigned char) '\0'; +} + +/* Freeing is what 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. Both leave the structs in the state an init produces, so the next + * Install parses into them again, and a second Release is harmless. */ +static void MbedTlsPemBufferCredentials_Release(struct SolidSyslogMbedTlsCredentials* base) +{ + struct SolidSyslogMbedTlsPemBufferCredentials* self = MbedTlsPemBufferCredentials_SelfFromBase(base); + mbedtls_x509_crt_free(&self->CaChain); + mbedtls_x509_crt_free(&self->ClientCertChain); + mbedtls_pk_free(&self->ClientKey); +} diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentialsPrivate.h b/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentialsPrivate.h new file mode 100644 index 00000000..c54243cd --- /dev/null +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentialsPrivate.h @@ -0,0 +1,46 @@ +/* SPDX-FileCopyrightText: Copyright 2026 Cozens Software Solutions Limited + * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial + */ + +#ifndef SOLIDSYSLOGMBEDTLSPEMBUFFERCREDENTIALSPRIVATE_H +#define SOLIDSYSLOGMBEDTLSPEMBUFFERCREDENTIALSPRIVATE_H + +#include +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" +#include "SolidSyslogMbedTlsPemBufferCredentials.h" +#include "SolidSyslogMbedTlsPemBufferCredentialsErrors.h" +#include "SolidSyslogPrival.h" + +struct SolidSyslogMbedTlsPemBufferCredentials +{ + struct SolidSyslogMbedTlsCredentials Base; + struct SolidSyslogMbedTlsPemBufferCredentialsConfig Config; + /* Holds the parsed material for the length of one connection only. Init'd + * at Create so the frees in Release are safe whether or not Install ever + * ran, and left in Mbed TLS's freed-equivalent state afterwards so the next + * Install parses into them again. */ + mbedtls_x509_crt CaChain; + mbedtls_x509_crt ClientCertChain; + mbedtls_pk_context ClientKey; +}; + +void SolidSyslogMbedTlsPemBufferCredentials_Initialise( + struct SolidSyslogMbedTlsCredentials* base, + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config +); +void SolidSyslogMbedTlsPemBufferCredentials_Cleanup(struct SolidSyslogMbedTlsCredentials* base); + +static inline void MbedTlsPemBufferCredentials_Report( + enum SolidSyslogSeverity severity, + uint16_t category, + enum SolidSyslogMbedTlsPemBufferCredentialsErrors code +) +{ + SolidSyslog_Error(severity, &SolidSyslogMbedTlsPemBufferCredentialsErrorSource, category, (int32_t) code); +} + +#endif /* SOLIDSYSLOGMBEDTLSPEMBUFFERCREDENTIALSPRIVATE_H */ diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentialsStatic.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentialsStatic.c new file mode 100644 index 00000000..8e5ab368 --- /dev/null +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentialsStatic.c @@ -0,0 +1,129 @@ +/* SPDX-FileCopyrightText: Copyright 2026 Cozens Software Solutions Limited + * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial + */ + +#include "SolidSyslogMbedTlsPemBufferCredentials.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorCategory.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" +#include "SolidSyslogMbedTlsPemBufferCredentialsPrivate.h" +#include "SolidSyslogMbedTlsNullCredentials.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +static inline bool MbedTlsPemBufferCredentials_IsValidConfig( + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config +); +static inline size_t MbedTlsPemBufferCredentials_IndexFromHandle(const struct SolidSyslogMbedTlsCredentials* base); +static inline void MbedTlsPemBufferCredentials_CleanupAtIndex(size_t index, void* context); + +static bool MbedTlsPemBufferCredentials_InUse[SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE]; +static struct SolidSyslogMbedTlsPemBufferCredentials + MbedTlsPemBufferCredentials_Pool[SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE]; +static struct SolidSyslogPoolAllocator MbedTlsPemBufferCredentials_Allocator = { + MbedTlsPemBufferCredentials_InUse, + SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE +}; + +struct SolidSyslogMbedTlsCredentials* SolidSyslogMbedTlsPemBufferCredentials_Create( + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config +) +{ + struct SolidSyslogMbedTlsCredentials* handle = SolidSyslogMbedTlsNullCredentials_Get(); + if (MbedTlsPemBufferCredentials_IsValidConfig(config)) + { + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&MbedTlsPemBufferCredentials_Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&MbedTlsPemBufferCredentials_Allocator, index) == true) + { + SolidSyslogMbedTlsPemBufferCredentials_Initialise(&MbedTlsPemBufferCredentials_Pool[index].Base, config); + handle = &MbedTlsPemBufferCredentials_Pool[index].Base; + } + else + { + MbedTlsPemBufferCredentials_Report( + SOLIDSYSLOG_POOL_EXHAUSTED_SEVERITY, + SOLIDSYSLOG_CAT_POOL_EXHAUSTED, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_POOL_EXHAUSTED + ); + } + } + return handle; +} + +/* The RNG is checked here rather than where it is used, so a wiring fault is + * one Create-time report instead of a surprise on the first connection - Mbed + * TLS needs one to parse a private key at all. */ +static inline bool MbedTlsPemBufferCredentials_IsValidConfig( + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config +) +{ + bool valid = false; + if (config == NULL) + { + MbedTlsPemBufferCredentials_Report( + SOLIDSYSLOG_BAD_CONFIG_FATAL_SEVERITY, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_NULL_CONFIG + ); + } + else if (config->Rng == NULL) + { + MbedTlsPemBufferCredentials_Report( + SOLIDSYSLOG_BAD_CONFIG_FATAL_SEVERITY, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_NULL_RNG + ); + } + else + { + valid = true; + } + return valid; +} + +void SolidSyslogMbedTlsPemBufferCredentials_Destroy(struct SolidSyslogMbedTlsCredentials* base) +{ + size_t index = MbedTlsPemBufferCredentials_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&MbedTlsPemBufferCredentials_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse( + &MbedTlsPemBufferCredentials_Allocator, + index, + MbedTlsPemBufferCredentials_CleanupAtIndex, + NULL + ); + if (!released) + { + MbedTlsPemBufferCredentials_Report( + SOLIDSYSLOG_UNKNOWN_DESTROY_SEVERITY, + SOLIDSYSLOG_CAT_UNKNOWN_DESTROY, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_UNKNOWN_DESTROY + ); + } +} + +static inline size_t MbedTlsPemBufferCredentials_IndexFromHandle(const struct SolidSyslogMbedTlsCredentials* base) +{ + size_t result = SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE; poolIndex++) + { + if (base == &MbedTlsPemBufferCredentials_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +/* Destroying a source part way through a connection must not leave a parsed + * private key in a slot the pool is about to hand out again. */ +static inline void MbedTlsPemBufferCredentials_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + SolidSyslogMbedTlsPemBufferCredentials_Cleanup(&MbedTlsPemBufferCredentials_Pool[index].Base); +} diff --git a/Tests/MbedTlsIntegration/CMakeLists.txt b/Tests/MbedTlsIntegration/CMakeLists.txt index 098e6fc0..3b525365 100644 --- a/Tests/MbedTlsIntegration/CMakeLists.txt +++ b/Tests/MbedTlsIntegration/CMakeLists.txt @@ -47,6 +47,7 @@ endforeach() add_executable(MbedTlsIntegrationTests main.cpp SolidSyslogMbedTlsStreamIntegrationTest.cpp + SolidSyslogMbedTlsPemBufferCredentialsTest.cpp SolidSyslogMbedTlsAesGcmPolicyIntegrationTest.cpp SocketStream.c MbedTlsTestCert.c @@ -55,6 +56,8 @@ add_executable(MbedTlsIntegrationTests ${CMAKE_SOURCE_DIR}/Tests/Support/SafeStringStandard.c ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamStatic.c + ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c + ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentialsStatic.c ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsNullCredentials.c @@ -74,6 +77,9 @@ target_link_libraries(MbedTlsIntegrationTests PRIVATE target_include_directories(MbedTlsIntegrationTests PRIVATE ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Interface + # The PEM-buffer credentials suite reads the source's own parsed state to + # prove Release let go of it, which needs the pack's private header. + ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source ${CMAKE_SOURCE_DIR}/Core/Interface ${CMAKE_SOURCE_DIR}/Core/Source ${CMAKE_SOURCE_DIR}/Tests diff --git a/Tests/MbedTlsIntegration/MbedTlsTestCert.c b/Tests/MbedTlsIntegration/MbedTlsTestCert.c index 0a7ede61..2768a1a9 100644 --- a/Tests/MbedTlsIntegration/MbedTlsTestCert.c +++ b/Tests/MbedTlsIntegration/MbedTlsTestCert.c @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include #include @@ -66,6 +68,32 @@ void MbedTlsTestCert_Create( mbedtls_x509_crt_parse_der(&out->Cert, &der.Bytes[der.StartOffset], der.Length); } +size_t MbedTlsTestCert_WriteCertPem(const struct MbedTlsTestCert* cert, unsigned char* buffer, size_t capacity) +{ + size_t written = 0U; + int rc = mbedtls_pem_write_buffer( + "-----BEGIN CERTIFICATE-----\n", + "-----END CERTIFICATE-----\n", + cert->Cert.raw.p, + cert->Cert.raw.len, + buffer, + capacity, + &written + ); + assert(rc == 0); + (void) rc; + /* mbedtls_pem_write_buffer counts the terminator in `written`. */ + return written; +} + +size_t MbedTlsTestCert_WriteKeyPem(const struct MbedTlsTestCert* cert, unsigned char* buffer, size_t capacity) +{ + int rc = mbedtls_pk_write_key_pem((mbedtls_pk_context*) &cert->Key, buffer, capacity); + assert(rc == 0); + (void) rc; + return strlen((const char*) buffer) + 1U; +} + void MbedTlsTestCert_Destroy(struct MbedTlsTestCert* cert) { mbedtls_x509_crt_free(&cert->Cert); diff --git a/Tests/MbedTlsIntegration/MbedTlsTestCert.h b/Tests/MbedTlsIntegration/MbedTlsTestCert.h index a9efdd00..fbf96af2 100644 --- a/Tests/MbedTlsIntegration/MbedTlsTestCert.h +++ b/Tests/MbedTlsIntegration/MbedTlsTestCert.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "SolidSyslogExternC.h" @@ -38,6 +39,13 @@ SOLIDSYSLOG_EXTERN_C_BEGIN mbedtls_ctr_drbg_context* rng ); + /* Re-emit the pair as PEM text, for tests driving a credentials source + that parses buffers rather than taking handles. Both write a + NUL-terminated string and return its length INCLUDING that terminator, + which is the length mbedTLS's own parsers want. */ + size_t MbedTlsTestCert_WriteCertPem(const struct MbedTlsTestCert* cert, unsigned char* buffer, size_t capacity); + size_t MbedTlsTestCert_WriteKeyPem(const struct MbedTlsTestCert* cert, unsigned char* buffer, size_t capacity); + void MbedTlsTestCert_Destroy(struct MbedTlsTestCert * cert); SOLIDSYSLOG_EXTERN_C_END diff --git a/Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp b/Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp new file mode 100644 index 00000000..1884c75a --- /dev/null +++ b/Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp @@ -0,0 +1,394 @@ +/* The credential window is only observable through the ssl_config Install + * writes to, and through the source's own parsed state - both of which mbedTLS + * and this pack keep private. This is the documented way in. */ +#define MBEDTLS_ALLOW_PRIVATE_ACCESS + +#include "CppUTest/TestHarness.h" + +extern "C" +{ +#include +#include +#include + +#include "MbedTlsTestCert.h" +#include "SolidSyslogError.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" +#include "SolidSyslogMbedTlsNullCredentials.h" +#include "SolidSyslogMbedTlsPemBufferCredentials.h" +#include "SolidSyslogMbedTlsPemBufferCredentialsErrors.h" +#include "SolidSyslogMbedTlsPemBufferCredentialsPrivate.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTlsCredentialsInstalled.h" +#include "SolidSyslogTunables.h" +} + +#include "SolidSyslogErrorCategory.h" + +namespace +{ +constexpr size_t PEM_BUFFER_BYTES = 4096; +} // namespace + +/* This suite links no ErrorHandlerFake - it builds against the real libmbedtls, + * as the rest of Tests/MbedTlsIntegration does, and capturing the last event + * directly is enough to pin which code a real fault produces. */ +static int PemBufferCapturedErrorCount; +static struct SolidSyslogErrorEvent PemBufferLastCapturedError; + +static void CapturePemBufferError(void* context, const struct SolidSyslogErrorEvent* event) +{ + (void) context; + PemBufferCapturedErrorCount++; + PemBufferLastCapturedError = *event; +} + +#define CHECK_PEM_BUFFER_ERROR_REPORTED(expectedSeverity, expectedCategory, expectedCode) \ + { \ + LONGS_EQUAL(1, PemBufferCapturedErrorCount); \ + LONGS_EQUAL((expectedSeverity), PemBufferLastCapturedError.Severity); \ + POINTERS_EQUAL(&SolidSyslogMbedTlsPemBufferCredentialsErrorSource, PemBufferLastCapturedError.Source); \ + UNSIGNED_LONGS_EQUAL((expectedCategory), PemBufferLastCapturedError.Category); \ + LONGS_EQUAL((expectedCode), PemBufferLastCapturedError.Detail); \ + } + +/* Nothing parsed is still held: Mbed TLS leaves a freed chain and a freed key + * in the state an init produces, so an empty slot is what a released source + * looks like. */ +static bool HoldsNoMaterial(struct SolidSyslogMbedTlsCredentials* base) +{ + const struct SolidSyslogMbedTlsPemBufferCredentials* self = + reinterpret_cast(base); + return (self->CaChain.raw.p == nullptr) && (self->ClientCertChain.raw.p == nullptr) && + (mbedtls_pk_get_type(&self->ClientKey) == MBEDTLS_PK_NONE); +} + +// clang-format off +TEST_GROUP(SolidSyslogMbedTlsPemBufferCredentials) +{ + mbedtls_entropy_context entropy = {}; + mbedtls_ctr_drbg_context rng = {}; + mbedtls_ssl_config conf = {}; + + struct MbedTlsTestCert ca = {}; + struct MbedTlsTestCert clientCert = {}; + unsigned char caPem[PEM_BUFFER_BYTES] = {}; + unsigned char clientCertPem[PEM_BUFFER_BYTES] = {}; + unsigned char clientKeyPem[PEM_BUFFER_BYTES] = {}; + + struct SolidSyslogMbedTlsPemBufferCredentialsConfig config = {}; + struct SolidSyslogMbedTlsCredentials* credentials = nullptr; + struct SolidSyslogTlsCredentialsInstalled installed = {}; + + void setup() override + { + PemBufferCapturedErrorCount = 0; + PemBufferLastCapturedError = {}; + SolidSyslog_SetErrorHandler(CapturePemBufferError, nullptr); + + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_init(&rng); + const unsigned char pers[] = "mbedtls-pem-buffer-test"; + mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, pers, sizeof(pers) - 1U); + mbedtls_ssl_config_init(&conf); + + struct MbedTlsTestCertConfig caConfig = {}; + caConfig.SubjectName = "CN=Test Root CA"; + caConfig.IsCa = 1; + MbedTlsTestCert_Create(&caConfig, &ca, &rng); + + struct MbedTlsTestCertConfig leafConfig = {}; + leafConfig.SubjectName = "CN=solidsyslog-test-client"; + leafConfig.Issuer = &ca; + MbedTlsTestCert_Create(&leafConfig, &clientCert, &rng); + + config.Rng = &rng; + config.CaPem.Bytes = caPem; + config.CaPem.Length = MbedTlsTestCert_WriteCertPem(&ca, caPem, sizeof(caPem)); + } + + void teardown() override + { + SolidSyslog_SetErrorHandler(nullptr, nullptr); + if (credentials != nullptr) + { + SolidSyslogMbedTlsPemBufferCredentials_Destroy(credentials); + } + mbedtls_ssl_config_free(&conf); + MbedTlsTestCert_Destroy(&clientCert); + MbedTlsTestCert_Destroy(&ca); + mbedtls_ctr_drbg_free(&rng); + mbedtls_entropy_free(&entropy); + } + + void GiveAClientCredential() + { + config.ClientCertPem.Bytes = clientCertPem; + config.ClientCertPem.Length = + MbedTlsTestCert_WriteCertPem(&clientCert, clientCertPem, sizeof(clientCertPem)); + config.ClientKeyPem.Bytes = clientKeyPem; + config.ClientKeyPem.Length = MbedTlsTestCert_WriteKeyPem(&clientCert, clientKeyPem, sizeof(clientKeyPem)); + } +}; + +// clang-format on + +TEST(SolidSyslogMbedTlsPemBufferCredentials, CreateReturnsAPooledHandle) +{ + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + CHECK_TRUE(credentials != SolidSyslogMbedTlsNullCredentials_Get()); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallParsesTheTrustAnchorsAndReportsThem) +{ + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + CHECK_TRUE(credentials->Install(credentials, &conf, &installed)); + + CHECK_TRUE(installed.TrustAnchorsInstalled); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, CreateWithNullConfigReturnsTheNullCredentials) +{ + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(nullptr); + + POINTERS_EQUAL(SolidSyslogMbedTlsNullCredentials_Get(), credentials); + credentials = nullptr; + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_SEVERITY_CRITICAL, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_NULL_CONFIG + ); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, CreateWithoutAnRngReturnsTheNullCredentials) +{ + config.Rng = nullptr; + + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + POINTERS_EQUAL(SolidSyslogMbedTlsNullCredentials_Get(), credentials); + credentials = nullptr; + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_SEVERITY_CRITICAL, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_NULL_RNG + ); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, CreateBeyondThePoolReportsExhaustion) +{ + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + struct SolidSyslogMbedTlsCredentials* overflow = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + POINTERS_EQUAL(SolidSyslogMbedTlsNullCredentials_Get(), overflow); + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_POOL_EXHAUSTED_SEVERITY, + SOLIDSYSLOG_CAT_POOL_EXHAUSTED, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_POOL_EXHAUSTED + ); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, DestroyingAHandleThePoolDoesNotOwnIsReported) +{ + struct SolidSyslogMbedTlsCredentials stranger = {}; + + SolidSyslogMbedTlsPemBufferCredentials_Destroy(&stranger); + + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_UNKNOWN_DESTROY_SEVERITY, + SOLIDSYSLOG_CAT_UNKNOWN_DESTROY, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_UNKNOWN_DESTROY + ); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallWiresTheParsedChainAsTheConfigurationsTrustAnchors) +{ + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + CHECK_TRUE(conf.MBEDTLS_PRIVATE(ca_chain) != nullptr); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallReportsNoFingerprints) +{ + const char* pin = "sha-256:AA"; + installed.Fingerprints = &pin; + installed.FingerprintCount = 1; + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + POINTERS_EQUAL(nullptr, installed.Fingerprints); + UNSIGNED_LONGS_EQUAL(0, installed.FingerprintCount); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallWithoutTrustAnchorsParsesNothingAndStillSucceeds) +{ + config.CaPem.Bytes = nullptr; + config.CaPem.Length = 0; + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + CHECK_TRUE(credentials->Install(credentials, &conf, &installed)); + + CHECK_FALSE(installed.TrustAnchorsInstalled); + LONGS_EQUAL(0, PemBufferCapturedErrorCount); +} + +/* The classic mistake: a length of strlen rather than strlen + 1. Mbed TLS + * would read the buffer as DER and fail as "not a certificate", so the fault is + * named here instead. */ +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallReportsATrustAnchorBufferThatIsNotTerminated) +{ + config.CaPem.Length -= 1U; + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + CHECK_FALSE(credentials->Install(credentials, &conf, &installed)); + + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_SEVERITY_ERROR, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_PEM_NOT_TERMINATED + ); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallReportsTrustAnchorsItCannotParse) +{ + caPem[20] = 'X'; /* corrupt the base64 body, leaving the terminator alone */ + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + CHECK_FALSE(credentials->Install(credentials, &conf, &installed)); + + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_SEVERITY_ERROR, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_TRUST_ANCHORS_NOT_PARSED + ); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallPresentsAParsedClientCredential) +{ + GiveAClientCredential(); + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + CHECK_TRUE(conf.MBEDTLS_PRIVATE(key_cert) != nullptr); + LONGS_EQUAL(0, PemBufferCapturedErrorCount); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallWithoutAClientCredentialPresentsNone) +{ + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + POINTERS_EQUAL(nullptr, conf.MBEDTLS_PRIVATE(key_cert)); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallReportsAHalfSuppliedClientCredential) +{ + GiveAClientCredential(); + config.ClientKeyPem.Bytes = nullptr; + config.ClientKeyPem.Length = 0; + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_INCOMPLETE + ); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallReportsAClientKeyItCannotParse) +{ + GiveAClientCredential(); + clientKeyPem[40] = 'X'; + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_NOT_PARSED + ); +} + +/* No fault in our own credential stops delivery - the connection continues + * server-authenticated and the collector decides whether to accept it. */ +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallStillSucceedsWhenTheClientCredentialIsFaulty) +{ + GiveAClientCredential(); + clientKeyPem[40] = 'X'; + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + CHECK_TRUE(credentials->Install(credentials, &conf, &installed)); + + POINTERS_EQUAL(nullptr, conf.MBEDTLS_PRIVATE(key_cert)); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallReportsAClientKeyThatDoesNotMatchItsCertificate) +{ + struct MbedTlsTestCert stranger = {}; + struct MbedTlsTestCertConfig strangerConfig = {}; + strangerConfig.SubjectName = "CN=someone-else"; + strangerConfig.Issuer = &ca; + MbedTlsTestCert_Create(&strangerConfig, &stranger, &rng); + + GiveAClientCredential(); + config.ClientKeyPem.Length = MbedTlsTestCert_WriteKeyPem(&stranger, clientKeyPem, sizeof(clientKeyPem)); + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_MISMATCHED + ); + POINTERS_EQUAL(nullptr, conf.MBEDTLS_PRIVATE(key_cert)); + + MbedTlsTestCert_Destroy(&stranger); +} + +/* The custody claim: what Install parsed is gone once the connection ends. */ +TEST(SolidSyslogMbedTlsPemBufferCredentials, ReleaseLetsGoOfEverythingItParsed) +{ + GiveAClientCredential(); + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + credentials->Install(credentials, &conf, &installed); + + credentials->Release(credentials); + + CHECK_TRUE(HoldsNoMaterial(credentials)); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, ReleaseWithoutAnInstallIsSafe) +{ + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + credentials->Release(credentials); + + CHECK_TRUE(HoldsNoMaterial(credentials)); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, ASecondInstallParsesAgainAfterARelease) +{ + GiveAClientCredential(); + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + credentials->Install(credentials, &conf, &installed); + credentials->Release(credentials); + mbedtls_ssl_config_free(&conf); + mbedtls_ssl_config_init(&conf); + + CHECK_TRUE(credentials->Install(credentials, &conf, &installed)); + + CHECK_TRUE(installed.TrustAnchorsInstalled); + CHECK_TRUE(conf.MBEDTLS_PRIVATE(key_cert) != nullptr); +} diff --git a/docs/generated/MbedTls-manifest.txt b/docs/generated/MbedTls-manifest.txt index 8833f123..a80e1351 100644 --- a/docs/generated/MbedTls-manifest.txt +++ b/docs/generated/MbedTls-manifest.txt @@ -15,6 +15,8 @@ # MbedTls: Platform/MbedTls/Source/SolidSyslogMbedTlsNullCredentials.c Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c +Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c +Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentialsStatic.c Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c Platform/MbedTls/Source/SolidSyslogMbedTlsStreamStatic.c diff --git a/docs/generated/beta-stack-manifest.txt b/docs/generated/beta-stack-manifest.txt index dc078d2a..97929552 100644 --- a/docs/generated/beta-stack-manifest.txt +++ b/docs/generated/beta-stack-manifest.txt @@ -77,6 +77,8 @@ Core/Source/SolidSyslogNullAtomicCounter.c # MbedTls: Platform/MbedTls/Source/SolidSyslogMbedTlsNullCredentials.c Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c +Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c +Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentialsStatic.c Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c Platform/MbedTls/Source/SolidSyslogMbedTlsStreamStatic.c diff --git a/docs/platforms/mbedtls/index.md b/docs/platforms/mbedtls/index.md index 4e15c16b..53ede8eb 100644 --- a/docs/platforms/mbedtls/index.md +++ b/docs/platforms/mbedtls/index.md @@ -30,24 +30,33 @@ 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. -One source ships with the pack: `SolidSyslogMbedTlsHandleCredentials`, which -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. No part -of the adapter opens a file, which is what allows it to run on targets built -without `MBEDTLS_FS_IO`. +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. A source that acquires -material per connection can therefore let go of it between connections, and one -carrying handles the integrator owns - the shipped source - keeps them for as -long as the integrator does. +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. -Rotation with the shipped source is a disconnect and a re-parse: call +Rotation with the handle source is a disconnect and a re-parse: call `SolidSyslogSender_Disconnect`, then free and re-parse into the same handle. The next send reconnects with the new material. Freeing before the disconnect -completes is a use-after-free, because the open connection is still reading -it. +completes is a use-after-free, because the open connection is still reading it. +With the PEM-buffer source, replacing the buffer is enough - the next connection +parses whatever it then points at. ## Coexistence is an auditable contract diff --git a/docs/platforms/mbedtls/setup.md b/docs/platforms/mbedtls/setup.md index 4b422d9d..cd13e29a 100644 --- a/docs/platforms/mbedtls/setup.md +++ b/docs/platforms/mbedtls/setup.md @@ -30,8 +30,8 @@ applies RFC 6587 octet-counting framing on top either way. ## Wiring it -First a credentials source, which is where the material comes from. The one that -ships with the pack carries handles you have already built: +First a credentials source, which is where the material comes from. Two ship +with the pack. The first carries handles you have already built: ```c struct SolidSyslogMbedTlsHandleCredentialsConfig credentialsConfig = { @@ -45,8 +45,33 @@ struct SolidSyslogMbedTlsCredentials* credentials = ``` The `Rng` here is what checks the client key against its certificate, and the -same seeded DRBG serves both configs. Then the stream, which is wired to the -source: +same seeded DRBG serves both configs. + +The second parses PEM you hold in memory, once per connection, and lets go of +what it parsed when the connection ends - so a device that connects rarely does +not keep a parsed private key in RAM in between: + +```c +struct SolidSyslogMbedTlsPemBufferCredentialsConfig credentialsConfig = { + .CaPem = {myCaPem, sizeof(myCaPem)}, + .ClientCertPem = {myClientCertPem, sizeof(myClientCertPem)}, + .ClientKeyPem = {myClientKeyPem, sizeof(myClientKeyPem)}, + .Rng = &mySeededDrbg, +}; +struct SolidSyslogMbedTlsCredentials* credentials = + SolidSyslogMbedTlsPemBufferCredentials_Create(&credentialsConfig); +``` + +**Each length must include the terminating NUL** - `strlen(pem) + 1` - because +that is what Mbed TLS's parsers require of PEM. A length one byte short is +reported rather than left to surface as a parse failure, which matters because +Mbed TLS reads a certificate buffer whose last byte is not NUL as DER instead +and fails as "not a certificate". Material baked in by `xxd -i` is the usual +place this bites: the array it emits carries no terminator, so give the buffer +one byte more and set it. The key must not be encrypted; no password can be +supplied. + +Then the stream, which is wired to whichever source you built: ```c struct SolidSyslogMbedTlsStreamConfig cfg = { diff --git a/misra_suppressions.txt b/misra_suppressions.txt index 4d5b6cee..e52387b2 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -56,6 +56,7 @@ misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpResolver.c:48 misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c:129 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:116 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c:59 +misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c:70 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsHmacSha256Policy.c:83 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicy.c:85 misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogOpenSslAesGcmPolicy.c:89 From c8833b6bd3322b7eddc58a538323e9c618673015 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 1 Sep 2026 21:32:51 +0100 Subject: [PATCH 2/4] docs: keep the credentials pool tunable to what the tunable decides --- Core/Interface/SolidSyslogTunablesDefaults.h | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index a01b1217..5372953a 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -593,16 +593,11 @@ /** * Role pool: TLS credentials. Number of credentials instances the library's - * internal static pool can simultaneously hold, across whichever backend is - * compiled in - a PEM source, caller-built vendor handles, or an - * integrator's own class reaching a secure element or key store. An instance - * carries where its material comes from; a backend that acquires material per - * connection also holds what it acquired, for the length of that connection. - * - * Default 1 - one source per TLS stream is the ordinary wiring, and the - * stream pool defaults to one. Bump it alongside - * SOLIDSYSLOG_TLS_STREAM_POOL_SIZE where several streams draw on separate - * sources. + * internal static pool can simultaneously hold. + * + * Default 1 - one source per TLS stream is the ordinary wiring. Bump it + * alongside SOLIDSYSLOG_TLS_STREAM_POOL_SIZE where several streams draw on + * separate sources. * * Floor: 1. Sub-floor values rejected at compile time. */ From fc49c599877507be721c3e0a7407f8bba6dfd69d Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 1 Sep 2026 21:52:55 +0100 Subject: [PATCH 3/4] fix: name an unterminated PEM buffer on the client path too The trust-anchor path reported PEM_NOT_TERMINATED and the client path folded the same fault into a parse failure, so the code existed on one path only. A pointer with no extent is now reported rather than read as material that was never supplied, and the length guard moves into the termination check that needs it. docs/NAMING.md distinguishes a boolean condition, which earns an is/has/can prefix, from a short-lived result local, which does not - matching what every file in Core and Platform already does. --- .../SolidSyslogMbedTlsPemBufferCredentials.c | 69 ++++++++++++------- ...dSyslogMbedTlsPemBufferCredentialsTest.cpp | 64 +++++++++++++++++ docs/NAMING.md | 14 +++- misra_suppressions.txt | 2 +- 4 files changed, 120 insertions(+), 29 deletions(-) diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c index 288c3937..fb1e04d1 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c @@ -37,6 +37,9 @@ static inline bool MbedTlsPemBufferCredentials_HasClientCredential( static inline bool MbedTlsPemBufferCredentials_HasHalfOfClientCredential( const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config ); +static inline bool MbedTlsPemBufferCredentials_ClientPemIsTerminated( + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config +); static inline bool MbedTlsPemBufferCredentials_ParseClientCredential(struct SolidSyslogMbedTlsPemBufferCredentials* self ); static inline bool MbedTlsPemBufferCredentials_ClientKeyMatchesCertificate( @@ -114,7 +117,15 @@ static inline void MbedTlsPemBufferCredentials_ConfigureClientIdentity( { if (MbedTlsPemBufferCredentials_HasClientCredential(&self->Config)) { - if (MbedTlsPemBufferCredentials_ParseClientCredential(self) == false) + if (MbedTlsPemBufferCredentials_ClientPemIsTerminated(&self->Config) == false) + { + MbedTlsPemBufferCredentials_Report( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_PEM_NOT_TERMINATED + ); + } + else if (MbedTlsPemBufferCredentials_ParseClientCredential(self) == false) { MbedTlsPemBufferCredentials_Report( SOLIDSYSLOG_SEVERITY_WARNING, @@ -177,31 +188,33 @@ static inline bool MbedTlsPemBufferCredentials_HasHalfOfClientCredential( MbedTlsPemBufferCredentials_IsSupplied(&config->ClientKeyPem); } -/* Both halves in one place: a certificate without its key is no more use than - * neither, so an unterminated or unparseable half fails the pair. */ +/* Both halves, because a certificate whose key is unusable is no more use than + * neither. */ +static inline bool MbedTlsPemBufferCredentials_ClientPemIsTerminated( + const struct SolidSyslogMbedTlsPemBufferCredentialsConfig* config +) +{ + return MbedTlsPemBufferCredentials_IsTerminated(&config->ClientCertPem) && + MbedTlsPemBufferCredentials_IsTerminated(&config->ClientKeyPem); +} + static inline bool MbedTlsPemBufferCredentials_ParseClientCredential(struct SolidSyslogMbedTlsPemBufferCredentials* self ) { - bool parsed = false; - if (MbedTlsPemBufferCredentials_IsTerminated(&self->Config.ClientCertPem) && - MbedTlsPemBufferCredentials_IsTerminated(&self->Config.ClientKeyPem)) - { - parsed = (mbedtls_x509_crt_parse( - &self->ClientCertChain, - self->Config.ClientCertPem.Bytes, - self->Config.ClientCertPem.Length - ) == 0) && - (mbedtls_pk_parse_key( - &self->ClientKey, - self->Config.ClientKeyPem.Bytes, - self->Config.ClientKeyPem.Length, - NULL, - 0U, - mbedtls_ctr_drbg_random, - self->Config.Rng - ) == 0); - } - return parsed; + return (mbedtls_x509_crt_parse( + &self->ClientCertChain, + self->Config.ClientCertPem.Bytes, + self->Config.ClientCertPem.Length + ) == 0) && + (mbedtls_pk_parse_key( + &self->ClientKey, + self->Config.ClientKeyPem.Bytes, + self->Config.ClientKeyPem.Length, + NULL, + 0U, + mbedtls_ctr_drbg_random, + self->Config.Rng + ) == 0); } /* mbedtls_ssl_conf_own_cert does not check the pair it is handed, and names this @@ -252,14 +265,20 @@ static inline bool MbedTlsPemBufferCredentials_ParseTrustAnchors( return parsed; } +/* A NULL buffer is the documented way to say "not supplied". A pointer with an + * unusable extent is a mistake instead, and is reported rather than read as a + * decision to go without - the integrator who made it believes the material is + * in force. */ static inline bool MbedTlsPemBufferCredentials_IsSupplied(const struct SolidSyslogMbedTlsPemBuffer* pem) { - return (pem->Bytes != NULL) && (pem->Length > 0U); + return pem->Bytes != NULL; } +/* Guards its own read: a zero length has no last byte to test, and would index + * at SIZE_MAX. */ static inline bool MbedTlsPemBufferCredentials_IsTerminated(const struct SolidSyslogMbedTlsPemBuffer* pem) { - return pem->Bytes[pem->Length - 1U] == (unsigned char) '\0'; + return (pem->Length > 0U) && (pem->Bytes[pem->Length - 1U] == (unsigned char) '\0'); } /* Freeing is what wipes: mbedtls_pk_free zeroises the key context and every diff --git a/Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp b/Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp index 1884c75a..4666744e 100644 --- a/Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp +++ b/Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp @@ -392,3 +392,67 @@ TEST(SolidSyslogMbedTlsPemBufferCredentials, ASecondInstallParsesAgainAfterARele CHECK_TRUE(installed.TrustAnchorsInstalled); CHECK_TRUE(conf.MBEDTLS_PRIVATE(key_cert) != nullptr); } + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallReportsATrustAnchorBufferWithNoExtent) +{ + config.CaPem.Length = 0; + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + CHECK_FALSE(credentials->Install(credentials, &conf, &installed)); + + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_SEVERITY_ERROR, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_PEM_NOT_TERMINATED + ); +} + +/* The same off-by-one on the client half must name the same fault the trust + * anchor path names, or the code exists on one path only. */ +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallReportsAClientCertificateThatIsNotTerminated) +{ + GiveAClientCredential(); + config.ClientCertPem.Length -= 1U; + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_PEM_NOT_TERMINATED + ); +} + +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallReportsAClientKeyThatIsNotTerminated) +{ + GiveAClientCredential(); + config.ClientKeyPem.Length -= 1U; + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_PEM_NOT_TERMINATED + ); +} + +/* A pointer with no extent is a mistake, not a decision to go without: read as + * absent it would leave the integrator believing mutual TLS was in force. */ +TEST(SolidSyslogMbedTlsPemBufferCredentials, InstallReportsAClientCredentialWithNoExtent) +{ + GiveAClientCredential(); + config.ClientCertPem.Length = 0; + config.ClientKeyPem.Length = 0; + credentials = SolidSyslogMbedTlsPemBufferCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + CHECK_PEM_BUFFER_ERROR_REPORTED( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_PEM_BUFFER_CREDENTIALS_ERROR_PEM_NOT_TERMINATED + ); +} diff --git a/docs/NAMING.md b/docs/NAMING.md index 6b8ba771..cc452ad0 100644 --- a/docs/NAMING.md +++ b/docs/NAMING.md @@ -380,8 +380,15 @@ Constraints: `struct mq_attr`, etc.). - **No pointer Hungarian.** Never prefix pointer variables with `p`/`P` or suffix with `Ptr`. Pointer-ness is visible from the declaration. -- **Booleans.** Predicates and boolean variables use `isX`, `hasX`, - or `canX` shapes (`isValid`, `hasUnsent`, `canSend`). +- **Booleans.** A boolean that names a *condition* takes an `isX`, `hasX` + or `canX` shape - a predicate function (`BlockSequence_IsAboveThreshold`), + or a variable whose name has to carry its meaning where it is read + (`isValid`, `hasUnsent`, `canSend`). A short-lived local holding the + outcome of the work the function has just done does not: `ok`, `parsed`, + `released` are read next to the call that produced them, and a prefix adds + length without adding meaning. The test is whether the name travels - a + condition passed around or tested far from where it was set earns the + prefix; a result returned three lines later does not. ### This-pointer parameters @@ -934,7 +941,8 @@ static inline bool CircularBuffer_IsEmpty(const struct SolidSyslogCircularBuffer | Function parameter / local | `lowerCamelCase` | `recordLength`, `bytesAvailable` | | This-pointer parameter | `self` (own type) / `base` (abstract base) | `* self` in helpers; `* base` in vtable impls | | Downcast helper | `Class_SelfFromBase` / `Class_SelfFromArg` | `CircularBuffer_SelfFromBase` | -| Boolean / predicate | `isX` / `hasX` / `canX` | `isValid`, `hasUnsent` | +| Boolean condition / predicate | `isX` / `hasX` / `canX` | `isValid`, `hasUnsent` | +| Boolean result local | short domain word, lowerCamelCase | `ok`, `parsed`, `released` | | Loop variable | short domain word, lowerCamelCase | `index`, `count`, `cursor` | | Struct member | `PascalCase` | `WriteCursor`, `IntegrityCheck`, `Write` (function-pointer member) | | Test group (class) | `SolidSyslogClassTest` | `SolidSyslogBufferTest` | diff --git a/misra_suppressions.txt b/misra_suppressions.txt index e52387b2..02594450 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -56,7 +56,7 @@ misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpResolver.c:48 misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c:129 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:116 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c:59 -misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c:70 +misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsPemBufferCredentials.c:73 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsHmacSha256Policy.c:83 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicy.c:85 misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogOpenSslAesGcmPolicy.c:89 From 0dba813c72000d1b18046a398765b88944686aa4 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 1 Sep 2026 22:07:33 +0100 Subject: [PATCH 4/4] fix: satisfy clang-tidy in the PEM-buffer credentials test files modernize-use-auto on the downcast the custody assertion makes, and a duplicate mbedtls/pk.h the PEM writers added. Both lanes that analyse this tree stop at the first error, so the second was only visible by reproducing the tidy preset locally. --- Tests/MbedTlsIntegration/MbedTlsTestCert.c | 3 +-- .../SolidSyslogMbedTlsPemBufferCredentialsTest.cpp | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/MbedTlsIntegration/MbedTlsTestCert.c b/Tests/MbedTlsIntegration/MbedTlsTestCert.c index 2768a1a9..769cc33d 100644 --- a/Tests/MbedTlsIntegration/MbedTlsTestCert.c +++ b/Tests/MbedTlsIntegration/MbedTlsTestCert.c @@ -3,11 +3,10 @@ #include #include #include +#include #include #include #include -#include -#include #include #include #include diff --git a/Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp b/Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp index 4666744e..8d965bb5 100644 --- a/Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp +++ b/Tests/MbedTlsIntegration/SolidSyslogMbedTlsPemBufferCredentialsTest.cpp @@ -57,8 +57,7 @@ static void CapturePemBufferError(void* context, const struct SolidSyslogErrorEv * looks like. */ static bool HoldsNoMaterial(struct SolidSyslogMbedTlsCredentials* base) { - const struct SolidSyslogMbedTlsPemBufferCredentials* self = - reinterpret_cast(base); + const auto* self = reinterpret_cast(base); return (self->CaChain.raw.p == nullptr) && (self->ClientCertChain.raw.p == nullptr) && (mbedtls_pk_get_type(&self->ClientKey) == MBEDTLS_PK_NONE); }