From 85cd1951c930e9212c4e4f1fdaa2b9fc2d59ff5c Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 1 Sep 2026 20:44:08 +0100 Subject: [PATCH 1/4] feat: an Mbed TLS credentials backend carrying caller-built handles --- Platform/MbedTls/CMakeLists.txt | 2 + .../SolidSyslogMbedTlsHandleCredentials.h | 64 +++ ...olidSyslogMbedTlsHandleCredentialsErrors.h | 39 ++ .../SolidSyslogMbedTlsHandleCredentials.c | 170 ++++++++ ...lidSyslogMbedTlsHandleCredentialsPrivate.h | 36 ++ ...olidSyslogMbedTlsHandleCredentialsStatic.c | 127 ++++++ Tests/MbedTls/CMakeLists.txt | 37 ++ ...olidSyslogMbedTlsHandleCredentialsTest.cpp | 384 ++++++++++++++++++ 8 files changed, 859 insertions(+) create mode 100644 Platform/MbedTls/Interface/SolidSyslogMbedTlsHandleCredentials.h create mode 100644 Platform/MbedTls/Interface/SolidSyslogMbedTlsHandleCredentialsErrors.h create mode 100644 Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c create mode 100644 Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsPrivate.h create mode 100644 Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c create mode 100644 Tests/MbedTls/SolidSyslogMbedTlsHandleCredentialsTest.cpp diff --git a/Platform/MbedTls/CMakeLists.txt b/Platform/MbedTls/CMakeLists.txt index b6dcc167..0ff65143 100644 --- a/Platform/MbedTls/CMakeLists.txt +++ b/Platform/MbedTls/CMakeLists.txt @@ -12,6 +12,8 @@ add_library(SolidSyslogMbedTls INTERFACE) # Unused TUs dead-strip at link time (-Wl,--gc-sections). target_sources(SolidSyslogMbedTls INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsNullCredentials.c + ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsHandleCredentials.c + ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsStream.c ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsStreamStatic.c ${CMAKE_CURRENT_SOURCE_DIR}/Source/SolidSyslogMbedTlsHmacSha256Policy.c diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsHandleCredentials.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHandleCredentials.h new file mode 100644 index 00000000..0c835f74 --- /dev/null +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHandleCredentials.h @@ -0,0 +1,64 @@ +/* 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 carries caller-built, caller-owned + * mbedTLS handles. The integrator parses its own PEM, unwraps its own key, or + * fetches material from wherever it lives, and hands the resulting handles + * here; this library parses nothing and owns nothing. + * + * The handles must outlive the credentials, because every connection installs + * the same ones. A backend that acquires material per connection - one parsing + * a PEM buffer on demand, or reaching a secure element - is a different + * implementation of the same role. */ +#ifndef SOLIDSYSLOGMBEDTLSHANDLECREDENTIALS_H +#define SOLIDSYSLOGMBEDTLSHANDLECREDENTIALS_H + +#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; +struct mbedtls_pk_context; +struct mbedtls_x509_crt; + +SOLIDSYSLOG_EXTERN_C_BEGIN + + struct SolidSyslogMbedTlsCredentials; + + /** Where this backend's material lives. Every handle is caller-built and + * caller-owned, and must stay valid for the lifetime of the credentials. */ + struct SolidSyslogMbedTlsHandleCredentialsConfig + { + /** Trust anchors the peer certificate must chain to; NULL installs + * none, which leaves the peer authorised only if the stream has + * another means to do it. */ + struct mbedtls_x509_crt* CaChain; + /** Leaf certificate (plus intermediates) for mutual TLS; NULL means no + * client credential. Certificate and key are all-or-nothing - + * supplying one without the other is reported. */ + struct mbedtls_x509_crt* ClientCertChain; + /** Private key matching ClientCertChain; NULL means no client + * credential. */ + struct mbedtls_pk_context* ClientKey; + /** Seeded CTR-DRBG, used to check the client key against its + * certificate; required - a NULL is reported at + * SolidSyslogMbedTlsHandleCredentials_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* SolidSyslogMbedTlsHandleCredentials_Create( + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config + ); + /** Release the pool slot. */ + void SolidSyslogMbedTlsHandleCredentials_Destroy(struct SolidSyslogMbedTlsCredentials * base); + +SOLIDSYSLOG_EXTERN_C_END + +#endif /* SOLIDSYSLOGMBEDTLSHANDLECREDENTIALS_H */ diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsHandleCredentialsErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHandleCredentialsErrors.h new file mode 100644 index 00000000..076462c5 --- /dev/null +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsHandleCredentialsErrors.h @@ -0,0 +1,39 @@ +/* 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 MbedTlsHandleCredentials backend. */ +#ifndef SOLIDSYSLOGMBEDTLSHANDLECREDENTIALSERRORS_H +#define SOLIDSYSLOGMBEDTLSHANDLECREDENTIALSERRORS_H + +#include "SolidSyslogExternC.h" + +SOLIDSYSLOG_EXTERN_C_BEGIN + + struct SolidSyslogErrorSource; + + /** Detail codes for events whose Source is SolidSyslogMbedTlsHandleCredentialsErrorSource. + * A handler reads these off event->Detail after matching event->Source; the + * members name their own fault. */ + enum SolidSyslogMbedTlsHandleCredentialsErrors + { + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_POOL_EXHAUSTED, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_UNKNOWN_DESTROY, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_NULL_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_NULL_RNG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_INCOMPLETE, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_MISMATCHED, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_NOT_INSTALLED, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_MAX /**< One past the last code; never emitted. Bounds the range for iteration. */ + }; + + /** Identity for events raised by an MbedTlsHandleCredentials. A handler + * matches by address (event->Source == &SolidSyslogMbedTlsHandleCredentialsErrorSource), + * then reads event->Detail as an enum + * SolidSyslogMbedTlsHandleCredentialsErrors. */ + extern const struct SolidSyslogErrorSource SolidSyslogMbedTlsHandleCredentialsErrorSource; + +SOLIDSYSLOG_EXTERN_C_END + +#endif /* SOLIDSYSLOGMBEDTLSHANDLECREDENTIALSERRORS_H */ diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c new file mode 100644 index 00000000..d65d6527 --- /dev/null +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c @@ -0,0 +1,170 @@ +/* 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 "SolidSyslogMbedTlsHandleCredentials.h" + +#include +#include +#include +#include +#include +#include + +#include "SolidSyslogErrorCategory.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" +#include "SolidSyslogMbedTlsHandleCredentialsPrivate.h" +#include "SolidSyslogTlsCredentialsInstalled.h" + +const struct SolidSyslogErrorSource SolidSyslogMbedTlsHandleCredentialsErrorSource = {"MbedTlsHandleCredentials"}; + +static bool MbedTlsHandleCredentials_Install( + struct SolidSyslogMbedTlsCredentials* base, + struct mbedtls_ssl_config* conf, + struct SolidSyslogTlsCredentialsInstalled* installed +); +static inline void MbedTlsHandleCredentials_ConfigureClientIdentity( + struct mbedtls_ssl_config* conf, + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +); +static inline bool MbedTlsHandleCredentials_HasClientCredential( + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +); +static inline bool MbedTlsHandleCredentials_ClientKeyMatchesCertificate( + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +); +static inline bool MbedTlsHandleCredentials_HasHalfOfClientCredential( + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +); +static void MbedTlsHandleCredentials_Release(struct SolidSyslogMbedTlsCredentials* base); +static inline struct SolidSyslogMbedTlsHandleCredentials* MbedTlsHandleCredentials_SelfFromBase( + struct SolidSyslogMbedTlsCredentials* base +); + +void SolidSyslogMbedTlsHandleCredentials_Initialise( + struct SolidSyslogMbedTlsCredentials* base, + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +) +{ + struct SolidSyslogMbedTlsHandleCredentials* self = MbedTlsHandleCredentials_SelfFromBase(base); + self->Base.Install = MbedTlsHandleCredentials_Install; + self->Base.Release = MbedTlsHandleCredentials_Release; + self->Config = *config; +} + +static inline struct SolidSyslogMbedTlsHandleCredentials* MbedTlsHandleCredentials_SelfFromBase( + struct SolidSyslogMbedTlsCredentials* base +) +{ + return (struct SolidSyslogMbedTlsHandleCredentials*) base; +} + +/* The handles are installed on every connection rather than held on the + * ssl_config across them, because mbedtls_ssl_config_free at Close takes the + * key_cert nodes with it - each Open builds the configuration again from what + * the integrator still owns. */ +static bool MbedTlsHandleCredentials_Install( + struct SolidSyslogMbedTlsCredentials* base, + struct mbedtls_ssl_config* conf, + struct SolidSyslogTlsCredentialsInstalled* installed +) +{ + struct SolidSyslogMbedTlsHandleCredentials* self = MbedTlsHandleCredentials_SelfFromBase(base); + installed->TrustAnchorsInstalled = self->Config.CaChain != NULL; + installed->Fingerprints = NULL; + installed->FingerprintCount = 0U; + if (installed->TrustAnchorsInstalled) + { + mbedtls_ssl_conf_ca_chain(conf, self->Config.CaChain, NULL); + } + MbedTlsHandleCredentials_ConfigureClientIdentity(conf, &self->Config); + return true; +} + +/* 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. */ +static inline void MbedTlsHandleCredentials_ConfigureClientIdentity( + struct mbedtls_ssl_config* conf, + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +) +{ + if (MbedTlsHandleCredentials_HasClientCredential(config)) + { + if (MbedTlsHandleCredentials_ClientKeyMatchesCertificate(config) == false) + { + MbedTlsHandleCredentials_Report( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_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, config->ClientCertChain, config->ClientKey) != 0) + { + MbedTlsHandleCredentials_Report( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_NOT_INSTALLED + ); + } + else + { + /* Paired and installed - the credential will be presented. */ + } + } + else if (MbedTlsHandleCredentials_HasHalfOfClientCredential(config)) + { + MbedTlsHandleCredentials_Report( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_INCOMPLETE + ); + } + else + { + /* Neither supplied - server-authenticated TLS is the deliberate case. */ + } +} + +static inline bool MbedTlsHandleCredentials_HasClientCredential( + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +) +{ + return (config->ClientCertChain != NULL) && (config->ClientKey != NULL); +} + +/* 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 MbedTlsHandleCredentials_ClientKeyMatchesCertificate( + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +) +{ + return mbedtls_pk_check_pair( + &config->ClientCertChain->pk, + config->ClientKey, + mbedtls_ctr_drbg_random, + config->Rng + ) == 0; +} + +/* 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 MbedTlsHandleCredentials_HasHalfOfClientCredential( + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +) +{ + return (config->ClientCertChain != NULL) != (config->ClientKey != NULL); +} + +/* Nothing to release. This backend carries handles the integrator built and + * still owns, and the ssl_config the stream frees at Close lets go of them. A + * backend that acquires material itself - one parsing a PEM buffer, or + * unwrapping a key - is where Release does real work. */ +static void MbedTlsHandleCredentials_Release(struct SolidSyslogMbedTlsCredentials* base) +{ + (void) base; +} diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsPrivate.h b/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsPrivate.h new file mode 100644 index 00000000..46ac6918 --- /dev/null +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsPrivate.h @@ -0,0 +1,36 @@ +/* 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 SOLIDSYSLOGMBEDTLSHANDLECREDENTIALSPRIVATE_H +#define SOLIDSYSLOGMBEDTLSHANDLECREDENTIALSPRIVATE_H + +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" +#include "SolidSyslogMbedTlsHandleCredentials.h" +#include "SolidSyslogMbedTlsHandleCredentialsErrors.h" +#include "SolidSyslogPrival.h" + +struct SolidSyslogMbedTlsHandleCredentials +{ + struct SolidSyslogMbedTlsCredentials Base; + struct SolidSyslogMbedTlsHandleCredentialsConfig Config; +}; + +void SolidSyslogMbedTlsHandleCredentials_Initialise( + struct SolidSyslogMbedTlsCredentials* base, + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +); + +static inline void MbedTlsHandleCredentials_Report( + enum SolidSyslogSeverity severity, + uint16_t category, + enum SolidSyslogMbedTlsHandleCredentialsErrors code +) +{ + SolidSyslog_Error(severity, &SolidSyslogMbedTlsHandleCredentialsErrorSource, category, (int32_t) code); +} + +#endif /* SOLIDSYSLOGMBEDTLSHANDLECREDENTIALSPRIVATE_H */ diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c new file mode 100644 index 00000000..6541b743 --- /dev/null +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c @@ -0,0 +1,127 @@ +/* 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 "SolidSyslogMbedTlsHandleCredentials.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorCategory.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" +#include "SolidSyslogMbedTlsHandleCredentialsPrivate.h" +#include "SolidSyslogMbedTlsNullCredentials.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +static inline bool MbedTlsHandleCredentials_IsValidConfig( + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +); +static inline size_t MbedTlsHandleCredentials_IndexFromHandle(const struct SolidSyslogMbedTlsCredentials* base); +static inline void MbedTlsHandleCredentials_CleanupAtIndex(size_t index, void* context); + +static bool MbedTlsHandleCredentials_InUse[SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE]; +static struct SolidSyslogMbedTlsHandleCredentials + MbedTlsHandleCredentials_Pool[SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE]; +static struct SolidSyslogPoolAllocator MbedTlsHandleCredentials_Allocator = { + MbedTlsHandleCredentials_InUse, + SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE +}; + +struct SolidSyslogMbedTlsCredentials* SolidSyslogMbedTlsHandleCredentials_Create( + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +) +{ + struct SolidSyslogMbedTlsCredentials* handle = SolidSyslogMbedTlsNullCredentials_Get(); + if (MbedTlsHandleCredentials_IsValidConfig(config)) + { + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&MbedTlsHandleCredentials_Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&MbedTlsHandleCredentials_Allocator, index) == true) + { + SolidSyslogMbedTlsHandleCredentials_Initialise(&MbedTlsHandleCredentials_Pool[index].Base, config); + handle = &MbedTlsHandleCredentials_Pool[index].Base; + } + else + { + MbedTlsHandleCredentials_Report( + SOLIDSYSLOG_POOL_EXHAUSTED_SEVERITY, + SOLIDSYSLOG_CAT_POOL_EXHAUSTED, + SOLIDSYSLOG_MBEDTLS_HANDLE_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 connection that first + * presents a client credential. */ +static inline bool MbedTlsHandleCredentials_IsValidConfig( + const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +) +{ + bool valid = false; + if (config == NULL) + { + MbedTlsHandleCredentials_Report( + SOLIDSYSLOG_BAD_CONFIG_FATAL_SEVERITY, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_NULL_CONFIG + ); + } + else if (config->Rng == NULL) + { + MbedTlsHandleCredentials_Report( + SOLIDSYSLOG_BAD_CONFIG_FATAL_SEVERITY, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_NULL_RNG + ); + } + else + { + valid = true; + } + return valid; +} + +void SolidSyslogMbedTlsHandleCredentials_Destroy(struct SolidSyslogMbedTlsCredentials* base) +{ + size_t index = MbedTlsHandleCredentials_IndexFromHandle(base); + bool released = SolidSyslogPoolAllocator_IndexIsValid(&MbedTlsHandleCredentials_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse( + &MbedTlsHandleCredentials_Allocator, + index, + MbedTlsHandleCredentials_CleanupAtIndex, + NULL + ); + if (!released) + { + MbedTlsHandleCredentials_Report( + SOLIDSYSLOG_UNKNOWN_DESTROY_SEVERITY, + SOLIDSYSLOG_CAT_UNKNOWN_DESTROY, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_UNKNOWN_DESTROY + ); + } +} + +static inline size_t MbedTlsHandleCredentials_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 == &MbedTlsHandleCredentials_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static inline void MbedTlsHandleCredentials_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + (void) index; +} diff --git a/Tests/MbedTls/CMakeLists.txt b/Tests/MbedTls/CMakeLists.txt index a3e63115..832a21f7 100644 --- a/Tests/MbedTls/CMakeLists.txt +++ b/Tests/MbedTls/CMakeLists.txt @@ -177,3 +177,40 @@ target_include_directories(SolidSyslogMbedTlsNullCredentialsTest PRIVATE ) add_test(NAME SolidSyslogMbedTlsNullCredentialsTest COMMAND SolidSyslogMbedTlsNullCredentialsTest) + +# SolidSyslogMbedTlsHandleCredentials — the credentials backend that carries +# caller-built, caller-owned mbedTLS handles. Driven against MbedTlsFake, which +# already interposes conf_ca_chain / conf_own_cert / pk_check_pair. +add_executable(SolidSyslogMbedTlsHandleCredentialsTest + SolidSyslogMbedTlsHandleCredentialsTest.cpp + main.cpp + ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c + ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c + ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsNullCredentials.c +) + +target_link_libraries(SolidSyslogMbedTlsHandleCredentialsTest PRIVATE + ${PROJECT_NAME} + ConfigLockFake + ErrorHandlerFake + MbedTlsFakes + CppUTest + CppUTestExt +) + +target_include_directories(SolidSyslogMbedTlsHandleCredentialsTest PRIVATE + ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Interface + ${CMAKE_SOURCE_DIR}/Core/Interface + ${CMAKE_SOURCE_DIR}/Core/Source + ${CMAKE_SOURCE_DIR}/Tests +) + +# The backend pulls in ; cppcheck cannot resolve mbedTLS's +# MBEDTLS_USER_CONFIG_FILE macro indirection. Disable per-target, same as the +# stream test exes. +set_target_properties(SolidSyslogMbedTlsHandleCredentialsTest PROPERTIES + C_CPPCHECK "" + CXX_CPPCHECK "" +) + +add_test(NAME SolidSyslogMbedTlsHandleCredentialsTest COMMAND SolidSyslogMbedTlsHandleCredentialsTest) diff --git a/Tests/MbedTls/SolidSyslogMbedTlsHandleCredentialsTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsHandleCredentialsTest.cpp new file mode 100644 index 00000000..be4f32fa --- /dev/null +++ b/Tests/MbedTls/SolidSyslogMbedTlsHandleCredentialsTest.cpp @@ -0,0 +1,384 @@ +#include "CppUTest/TestHarness.h" +#include "mbedtls/pk.h" +#include "mbedtls/x509_crt.h" + +extern "C" +{ +#include +#include + +#include "ErrorHandlerFake.h" +#include "MbedTlsFake.h" +#include "SolidSyslogError.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" +#include "SolidSyslogMbedTlsHandleCredentials.h" +#include "SolidSyslogMbedTlsHandleCredentialsErrors.h" +#include "SolidSyslogMbedTlsNullCredentials.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTlsCredentialsInstalled.h" +#include "SolidSyslogTunables.h" +} + +#include "SolidSyslogErrorCategory.h" +#include "TestUtils.h" + +using namespace CososoTesting; + +// clang-format off +TEST_GROUP(SolidSyslogMbedTlsHandleCredentials) +{ + mbedtls_ctr_drbg_context rng = {}; + mbedtls_x509_crt caChain = {}; + mbedtls_x509_crt clientCert = {}; + mbedtls_pk_context clientKey = {}; + mbedtls_ssl_config conf = {}; + struct SolidSyslogTlsCredentialsInstalled installed = {}; + struct SolidSyslogMbedTlsHandleCredentialsConfig config = {}; + struct SolidSyslogMbedTlsCredentials* credentials = nullptr; + struct SolidSyslogMbedTlsCredentials* pooled[SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE] = {}; + struct SolidSyslogMbedTlsCredentials* overflow = nullptr; + + void setup() override + { + MbedTlsFake_Reset(); + config.Rng = &rng; + config.CaChain = &caChain; + } + + void teardown() override + { + if (credentials != nullptr) + { + SolidSyslogMbedTlsHandleCredentials_Destroy(credentials); + } + for (auto* slot : pooled) + { + if (slot != nullptr) + { + SolidSyslogMbedTlsHandleCredentials_Destroy(slot); + } + } + if (overflow != nullptr) + { + SolidSyslogMbedTlsHandleCredentials_Destroy(overflow); + } + } + + /* Wires a client credential - either half may be null. */ + void GiveAClientCredential() + { + config.ClientCertChain = &clientCert; + config.ClientKey = &clientKey; + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogMbedTlsHandleCredentials_Create(&config); + } + } +}; + +// clang-format on + +TEST(SolidSyslogMbedTlsHandleCredentials, CreateReturnsAPooledHandle) +{ + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + CHECK_TRUE(credentials != SolidSyslogMbedTlsNullCredentials_Get()); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, CreateWithNullConfigReturnsTheNullCredentials) +{ + credentials = SolidSyslogMbedTlsHandleCredentials_Create(nullptr); + + POINTERS_EQUAL(SolidSyslogMbedTlsNullCredentials_Get(), credentials); + credentials = nullptr; +} + +TEST(SolidSyslogMbedTlsHandleCredentials, CreateWithNullConfigReportsBadConfig) +{ + ErrorHandlerFake_Install(nullptr); + + SolidSyslogMbedTlsHandleCredentials_Create(nullptr); + + CHECK_ERROR_REPORTED_ONCE( + SOLIDSYSLOG_SEVERITY_CRITICAL, + &SolidSyslogMbedTlsHandleCredentialsErrorSource, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_NULL_CONFIG + ); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, CreateWithoutAnRngReturnsTheNullCredentials) +{ + config.Rng = nullptr; + + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + POINTERS_EQUAL(SolidSyslogMbedTlsNullCredentials_Get(), credentials); + credentials = nullptr; +} + +TEST(SolidSyslogMbedTlsHandleCredentials, CreateWithoutAnRngReportsBadConfig) +{ + config.Rng = nullptr; + ErrorHandlerFake_Install(nullptr); + + SolidSyslogMbedTlsHandleCredentials_Create(&config); + + CHECK_ERROR_REPORTED_ONCE( + SOLIDSYSLOG_SEVERITY_CRITICAL, + &SolidSyslogMbedTlsHandleCredentialsErrorSource, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_NULL_RNG + ); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, CreateBeyondThePoolReturnsTheNullCredentials) +{ + FillPool(); + + overflow = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + POINTERS_EQUAL(SolidSyslogMbedTlsNullCredentials_Get(), overflow); + overflow = nullptr; +} + +TEST(SolidSyslogMbedTlsHandleCredentials, CreateBeyondThePoolReportsExhaustion) +{ + FillPool(); + ErrorHandlerFake_Install(nullptr); + + overflow = SolidSyslogMbedTlsHandleCredentials_Create(&config); + overflow = nullptr; + + CHECK_ERROR_REPORTED_ONCE( + SOLIDSYSLOG_POOL_EXHAUSTED_SEVERITY, + &SolidSyslogMbedTlsHandleCredentialsErrorSource, + SOLIDSYSLOG_CAT_POOL_EXHAUSTED, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_POOL_EXHAUSTED + ); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, DestroyingAHandleThePoolDoesNotOwnIsReported) +{ + struct SolidSyslogMbedTlsCredentials stranger = {}; + ErrorHandlerFake_Install(nullptr); + + SolidSyslogMbedTlsHandleCredentials_Destroy(&stranger); + + CHECK_ERROR_REPORTED_ONCE( + SOLIDSYSLOG_UNKNOWN_DESTROY_SEVERITY, + &SolidSyslogMbedTlsHandleCredentialsErrorSource, + SOLIDSYSLOG_CAT_UNKNOWN_DESTROY, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_UNKNOWN_DESTROY + ); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallWiresTheConfiguredCaChainWithNoRevocationList) +{ + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + LONGS_EQUAL(1, MbedTlsFake_SslConfCaChainCallCount()); + POINTERS_EQUAL(&conf, MbedTlsFake_LastSslConfCaChainConfigArg()); + POINTERS_EQUAL(&caChain, MbedTlsFake_LastSslConfCaChainArg()); + POINTERS_EQUAL(nullptr, MbedTlsFake_LastSslConfCaChainCrlArg()); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallReportsTheTrustAnchorsItInstalled) +{ + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + CHECK_TRUE(installed.TrustAnchorsInstalled); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallSucceeds) +{ + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + CHECK_TRUE(credentials->Install(credentials, &conf, &installed)); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallWithoutACaChainWiresNoTrustAnchors) +{ + config.CaChain = nullptr; + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + LONGS_EQUAL(0, MbedTlsFake_SslConfCaChainCallCount()); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallWithoutACaChainReportsNoTrustAnchors) +{ + config.CaChain = nullptr; + installed.TrustAnchorsInstalled = true; + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + CHECK_FALSE(installed.TrustAnchorsInstalled); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallReportsNoFingerprints) +{ + const char* pin = "sha-256:AA"; + installed.Fingerprints = &pin; + installed.FingerprintCount = 1; + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + POINTERS_EQUAL(nullptr, installed.Fingerprints); + UNSIGNED_LONGS_EQUAL(0, installed.FingerprintCount); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallPresentsTheConfiguredClientCredential) +{ + GiveAClientCredential(); + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + LONGS_EQUAL(1, MbedTlsFake_SslConfOwnCertCallCount()); + POINTERS_EQUAL(&conf, MbedTlsFake_LastSslConfOwnCertConfigArg()); + POINTERS_EQUAL(&clientCert, MbedTlsFake_LastSslConfOwnCertCertArg()); + POINTERS_EQUAL(&clientKey, MbedTlsFake_LastSslConfOwnCertKeyArg()); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallChecksTheClientKeyAgainstItsCertificate) +{ + GiveAClientCredential(); + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + LONGS_EQUAL(1, MbedTlsFake_PkCheckPairCallCount()); + POINTERS_EQUAL(&clientCert.pk, MbedTlsFake_LastPkCheckPairPublicKeyArg()); + POINTERS_EQUAL(&clientKey, MbedTlsFake_LastPkCheckPairPrivateKeyArg()); + POINTERS_EQUAL((void*) mbedtls_ctr_drbg_random, (void*) MbedTlsFake_LastPkCheckPairRngFuncArg()); + POINTERS_EQUAL(&rng, MbedTlsFake_LastPkCheckPairRngContextArg()); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallWithoutAClientCredentialPresentsNone) +{ + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + LONGS_EQUAL(0, MbedTlsFake_SslConfOwnCertCallCount()); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallWithOnlyAClientCertificatePresentsNone) +{ + config.ClientCertChain = &clientCert; + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + + credentials->Install(credentials, &conf, &installed); + + LONGS_EQUAL(0, MbedTlsFake_SslConfOwnCertCallCount()); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallReportsAHalfSuppliedClientCredential) +{ + config.ClientCertChain = &clientCert; + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + ErrorHandlerFake_Install(nullptr); + + credentials->Install(credentials, &conf, &installed); + + CHECK_ERROR_REPORTED_ONCE( + SOLIDSYSLOG_SEVERITY_WARNING, + &SolidSyslogMbedTlsHandleCredentialsErrorSource, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_INCOMPLETE + ); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallReportsAHalfSuppliedClientKey) +{ + config.ClientKey = &clientKey; + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + ErrorHandlerFake_Install(nullptr); + + credentials->Install(credentials, &conf, &installed); + + CHECK_ERROR_REPORTED_ONCE( + SOLIDSYSLOG_SEVERITY_WARNING, + &SolidSyslogMbedTlsHandleCredentialsErrorSource, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_INCOMPLETE + ); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallReportsAClientKeyThatDoesNotMatchItsCertificate) +{ + GiveAClientCredential(); + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + MbedTlsFake_SetPkCheckPairReturn(MBEDTLS_ERR_PK_TYPE_MISMATCH); + ErrorHandlerFake_Install(nullptr); + + credentials->Install(credentials, &conf, &installed); + + CHECK_ERROR_REPORTED_ONCE( + SOLIDSYSLOG_SEVERITY_WARNING, + &SolidSyslogMbedTlsHandleCredentialsErrorSource, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_MISMATCHED + ); +} + +/* A credential the collector would reject at CertificateVerify is worse than + * none: it moves the diagnosis to the far end. */ +TEST(SolidSyslogMbedTlsHandleCredentials, InstallDoesNotPresentAClientKeyThatDoesNotMatchItsCertificate) +{ + GiveAClientCredential(); + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + MbedTlsFake_SetPkCheckPairReturn(MBEDTLS_ERR_PK_TYPE_MISMATCH); + + credentials->Install(credentials, &conf, &installed); + + LONGS_EQUAL(0, MbedTlsFake_SslConfOwnCertCallCount()); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, InstallReportsAClientCredentialThatWillNotInstall) +{ + GiveAClientCredential(); + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + MbedTlsFake_SetSslConfOwnCertReturn(MBEDTLS_ERR_SSL_ALLOC_FAILED); + ErrorHandlerFake_Install(nullptr); + + credentials->Install(credentials, &conf, &installed); + + CHECK_ERROR_REPORTED_ONCE( + SOLIDSYSLOG_SEVERITY_WARNING, + &SolidSyslogMbedTlsHandleCredentialsErrorSource, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_HANDLE_CREDENTIALS_ERROR_CLIENT_CREDENTIAL_NOT_INSTALLED + ); +} + +/* No fault in our own credential stops delivery - the connection continues + * server-authenticated. */ +TEST(SolidSyslogMbedTlsHandleCredentials, InstallStillSucceedsWhenTheClientCredentialIsFaulty) +{ + GiveAClientCredential(); + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + MbedTlsFake_SetSslConfOwnCertReturn(MBEDTLS_ERR_SSL_ALLOC_FAILED); + + CHECK_TRUE(credentials->Install(credentials, &conf, &installed)); +} + +TEST(SolidSyslogMbedTlsHandleCredentials, ReleaseDoesNotCrash) +{ + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&config); + credentials->Install(credentials, &conf, &installed); + + credentials->Release(credentials); +} From aa34926426e6e0d5cd569e39a7d191a88910a6d6 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 1 Sep 2026 20:48:16 +0100 Subject: [PATCH 2/4] feat!: the Mbed TLS stream asks a credentials source for its material --- .../Interface/SolidSyslogMbedTlsStream.h | 40 +-- .../SolidSyslogMbedTlsStreamErrors.h | 5 +- .../MbedTls/Source/SolidSyslogMbedTlsStream.c | 108 ++++---- .../Source/SolidSyslogMbedTlsStreamPrivate.h | 6 + .../Source/SolidSyslogMbedTlsStreamStatic.c | 8 + Tests/MbedTls/CMakeLists.txt | 2 + .../SolidSyslogMbedTlsStreamPoolTest.cpp | 28 +++ .../MbedTls/SolidSyslogMbedTlsStreamTest.cpp | 234 ++++++++---------- Tests/MbedTlsCredentialsFake.c | 101 ++++++++ Tests/MbedTlsCredentialsFake.h | 36 +++ 10 files changed, 360 insertions(+), 208 deletions(-) create mode 100644 Tests/MbedTlsCredentialsFake.c create mode 100644 Tests/MbedTlsCredentialsFake.h diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h index 7a74293c..56defa30 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStream.h @@ -11,8 +11,9 @@ * What the stream does through its vtable is the substance: * * - Open first opens the underlying transport, applies the library-owned TLS - * policy (client mode, TLS 1.2 floor, VERIFY_REQUIRED against the CaChain), - * installs the peer identity, then drives the handshake to completion. The + * policy (client mode, TLS 1.2 floor, VERIFY_REQUIRED), asks the credentials + * source to install the material for this connection, installs the peer + * identity, then drives the handshake to completion. The * non-blocking transport means each mbedtls_ssl_handshake may want more I/O; * the injected Sleep bridges those polls until the handshake completes, hits * a hard error (HANDSHAKE_REJECTED), or the bounded budget expires @@ -25,10 +26,12 @@ * other TLS return (alert, transport error) - fail-fast, and store-and-forward * replays after the reconnect. * - * Peer identity is set by ServerName (see the config member). All key material - * is injected as caller-built, caller-owned mbedTLS handles - never file paths - * or PEM blobs. Coexistence contract: this adapter touches only per-instance - * ssl_config / ssl_context state and never calls process-global mbedTLS APIs + * Peer identity is set by ServerName (see the config member). No key material + * reaches this stream: it asks its credentials source to install onto the + * ssl_config at Open and tells it at Close, so a deployment can keep material + * out of memory between connections. Coexistence contract: this adapter touches + * only per-instance ssl_config / ssl_context state and never calls + * process-global mbedTLS APIs * (platform setup/teardown, psa_crypto_init, threading-alt, debug hooks), so it * drops into an integrator process that already uses Mbed TLS elsewhere. See * docs/platforms/mbedtls/setup.md. */ @@ -40,13 +43,12 @@ #include "SolidSyslogTlsHandshakeTimeoutFunction.h" struct SolidSyslogStream; +struct SolidSyslogMbedTlsCredentials; /* Forward declarations keep the public header free of any mbedTLS include. * Integrators include the relevant mbedTLS headers themselves before this - * one to bring the types into scope. See project_mbedtls_di_handles. */ + * one to bring the types into scope. */ struct mbedtls_ctr_drbg_context; -struct mbedtls_x509_crt; -struct mbedtls_pk_context; SOLIDSYSLOG_EXTERN_C_BEGIN @@ -58,6 +60,13 @@ SOLIDSYSLOG_EXTERN_C_BEGIN * destroys it; the caller owns it and must keep it valid until * SolidSyslogMbedTlsStream_Destroy. */ struct SolidSyslogStream* Transport; + /** Where the trust anchors, any pinned peer fingerprints and the mutual-TLS + * client credential come from; required - a NULL is reported at + * SolidSyslogMbedTlsStream_Create. Asked once per connection, so material + * is fetched only for a connection actually being made, and told when the + * connection ends. Borrowed - the caller owns it and must keep it valid + * until SolidSyslogMbedTlsStream_Destroy. */ + struct SolidSyslogMbedTlsCredentials* Credentials; SolidSyslogSleepFunction Sleep; /**< Bridges the WANT_READ/WANT_WRITE polls of the bounded handshake retry; required - a NULL is reported at SolidSyslogMbedTlsStream_Create. */ @@ -67,23 +76,18 @@ SOLIDSYSLOG_EXTERN_C_BEGIN struct mbedtls_ctr_drbg_context* Rng; /**< Seeded CTR-DRBG for the handshake; caller-built and caller-owned. Required - a NULL is reported at SolidSyslogMbedTlsStream_Create. */ - struct mbedtls_x509_crt* CaChain; /**< Trust anchors the peer cert must chain to; caller-built and owned. */ /** SNI + peer-identity check. A non-empty name is verified against the peer * cert (SAN/CN). NULL connects chain-only but emits a WARNING - the peer is * unverified (MITM-class). "" is the no-name-check opt-out (closed network / - * private CA): the cert must still chain to CaChain, but the endpoint - * identity is not checked; no diagnostic. */ + * private CA): the peer must still satisfy whatever the credentials + * installed, but the endpoint identity is not checked; no diagnostic. */ const char* ServerName; - struct mbedtls_x509_crt* ClientCertChain; /**< mTLS leaf (+ intermediates); caller-owned. NULL (or a NULL - ClientKey) disables mTLS - both must be set to present a client cert. */ - struct mbedtls_pk_context* ClientKey; /**< Private key matching ClientCertChain; caller-owned. NULL disables - mTLS. */ }; /** Draw a TLS stream from the pool over the config's Transport (see the file * overview for the handshake and I/O behaviour). A NULL config, a NULL - * Transport, a NULL Sleep or a NULL Rng is reported and falls back to the - * shared NullStream, as does an exhausted pool. */ + * Transport, a NULL Sleep, a NULL Rng or a NULL Credentials is reported and + * falls back to the shared NullStream, as does an exhausted pool. */ struct SolidSyslogStream* SolidSyslogMbedTlsStream_Create(const struct SolidSyslogMbedTlsStreamConfig* config); /** Release the pool slot; closes the TLS session and the underlying transport * if the stream is still open. */ diff --git a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h index 26a02fc3..dafc20e9 100644 --- a/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h +++ b/Platform/MbedTls/Interface/SolidSyslogMbedTlsStreamErrors.h @@ -27,13 +27,12 @@ SOLIDSYSLOG_EXTERN_C_BEGIN SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_SERVER_NAME_NOT_SET, SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_HANDSHAKE_REJECTED, SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_HANDSHAKE_TIMEOUT, - SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_CLIENT_CREDENTIAL_INCOMPLETE, - SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_CLIENT_CREDENTIAL_MISMATCHED, - SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_CLIENT_CREDENTIAL_NOT_INSTALLED, SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NULL_CONFIG, SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NULL_TRANSPORT, SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NULL_SLEEP, SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NULL_RNG, + SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NULL_CREDENTIALS, + SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NO_PEER_AUTHORISATION, SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_PEER_CERTIFICATE_UNTRUSTED, SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_PEER_NAME_MISMATCHED, SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_PEER_CERTIFICATE_EXPIRED, diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c index 8523bb1f..3aa3443b 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c @@ -5,7 +5,6 @@ #include "SolidSyslogMbedTlsStream.h" #include -#include #include #include #include @@ -14,12 +13,14 @@ #include "SolidSyslogError.h" #include "SolidSyslogErrorCategory.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" #include "SolidSyslogMbedTlsStreamErrors.h" #include "SolidSyslogMbedTlsStreamPrivate.h" #include "SolidSyslogNullStream.h" #include "SolidSyslogPrival.h" #include "SolidSyslogStream.h" #include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTlsCredentialsInstalled.h" #include "SolidSyslogTlsStreamCategories.h" #include "SolidSyslogTunables.h" @@ -39,9 +40,9 @@ static inline struct SolidSyslogMbedTlsStream* MbedTlsStream_SelfFromBase(struct static inline bool MbedTlsStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr); static inline bool MbedTlsStream_ApplySslConfigDefaults(struct SolidSyslogMbedTlsStream* self); static inline void MbedTlsStream_ApplyTlsPolicy(struct SolidSyslogMbedTlsStream* self); -static inline bool MbedTlsStream_HasClientCredential(const struct SolidSyslogMbedTlsStreamConfig* config); -static inline bool MbedTlsStream_ClientKeyMatchesCertificate(const struct SolidSyslogMbedTlsStreamConfig* config); -static inline bool MbedTlsStream_HasHalfOfClientCredential(const struct SolidSyslogMbedTlsStreamConfig* config); +static inline bool MbedTlsStream_InstallCredentials(struct SolidSyslogMbedTlsStream* self); +static inline bool MbedTlsStream_PeerIsAuthorisable(const struct SolidSyslogTlsCredentialsInstalled* installed); +static inline void MbedTlsStream_ReleaseCredentials(struct SolidSyslogMbedTlsStream* self); static inline bool MbedTlsStream_BindContextToConfig(struct SolidSyslogMbedTlsStream* self); static inline bool MbedTlsStream_ConfigureExpectedHostname(struct SolidSyslogMbedTlsStream* self); static inline void MbedTlsStream_InstallTransportCallbacks(struct SolidSyslogMbedTlsStream* self); @@ -69,6 +70,7 @@ void SolidSyslogMbedTlsStream_Initialise( self->Base.Read = MbedTlsStream_Read; self->Base.Close = MbedTlsStream_Close; self->Config = *config; + self->CredentialsInstalled = false; if (MbedTlsStream_ConfigProvidesHandshakeGetter(config) == false) { /* Substitute the Null Object so the bounded-handshake loop has a @@ -133,7 +135,11 @@ static inline void MbedTlsStream_Close(struct SolidSyslogStream* base) struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base); (void) mbedtls_ssl_close_notify(&self->SslContext); mbedtls_ssl_free(&self->SslContext); + /* The ssl_config holds the caller's certificates in its key_cert nodes until + it is freed, so the credentials are told the window has closed only after + mbedTLS has let go of them. */ mbedtls_ssl_config_free(&self->SslConfig); + MbedTlsStream_ReleaseCredentials(self); SolidSyslogStream_Close(self->Config.Transport); } @@ -144,7 +150,8 @@ static inline bool MbedTlsStream_Open(struct SolidSyslogStream* base, const stru if (ok) { MbedTlsStream_ApplyTlsPolicy(self); - ok = MbedTlsStream_BindContextToConfig(self) && MbedTlsStream_ConfigureExpectedHostname(self); + ok = MbedTlsStream_InstallCredentials(self) && MbedTlsStream_BindContextToConfig(self) && + MbedTlsStream_ConfigureExpectedHostname(self); } if (ok) { @@ -178,12 +185,9 @@ static inline bool MbedTlsStream_ApplySslConfigDefaults(struct SolidSyslogMbedTl } /* TLS policy owned by the library - set per-ssl_config so it cannot leak - * into the integrator's other ssl_configs (per coexistence contract). */ -/* 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. */ + * into the integrator's other ssl_configs (per coexistence contract). The + * material the policy is enforced against is not set here: the credentials + * source installs that, so this stream holds none of it. */ static inline void MbedTlsStream_ApplyTlsPolicy(struct SolidSyslogMbedTlsStream* self) { mbedtls_ssl_conf_authmode(&self->SslConfig, MBEDTLS_SSL_VERIFY_REQUIRED); @@ -194,69 +198,49 @@ static inline void MbedTlsStream_ApplyTlsPolicy(struct SolidSyslogMbedTlsStream* * RFC 9662, which updates RFC 5425, requires TLS 1.3 to be preferred * wherever it is implemented. */ mbedtls_ssl_conf_min_tls_version(&self->SslConfig, MBEDTLS_SSL_VERSION_TLS1_2); - mbedtls_ssl_conf_ca_chain(&self->SslConfig, self->Config.CaChain, NULL); mbedtls_ssl_conf_rng(&self->SslConfig, mbedtls_ctr_drbg_random, self->Config.Rng); - if (MbedTlsStream_HasClientCredential(&self->Config)) - { - if (MbedTlsStream_ClientKeyMatchesCertificate(&self->Config) == false) - { - MbedTlsStream_Report( - SOLIDSYSLOG_SEVERITY_WARNING, - SOLIDSYSLOG_CAT_BAD_CONFIG, - SOLIDSYSLOG_MBEDTLS_STREAM_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(&self->SslConfig, self->Config.ClientCertChain, self->Config.ClientKey) != 0) - { - MbedTlsStream_Report( - SOLIDSYSLOG_SEVERITY_WARNING, - SOLIDSYSLOG_CAT_BAD_CONFIG, - SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_CLIENT_CREDENTIAL_NOT_INSTALLED - ); - } - else - { - /* Paired and installed - the credential will be presented. */ - } - } - else if (MbedTlsStream_HasHalfOfClientCredential(&self->Config)) +} + +/* Asked once per connection, after the policy is on the ssl_config and before + * the session binds to it, so material is fetched only for a connection + * actually being made. The flag is set before the call rather than after it: + * the contract is one Release per Install call whatever that call returned, + * which is what spares every backend a rollback path of its own. */ +static inline bool MbedTlsStream_InstallCredentials(struct SolidSyslogMbedTlsStream* self) +{ + struct SolidSyslogTlsCredentialsInstalled installed = {false, NULL, 0U}; + self->CredentialsInstalled = true; + bool ok = self->Config.Credentials->Install(self->Config.Credentials, &self->SslConfig, &installed); + if (ok && !MbedTlsStream_PeerIsAuthorisable(&installed)) { MbedTlsStream_Report( - SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_CAT_BAD_CONFIG, - SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_CLIENT_CREDENTIAL_INCOMPLETE + SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NO_PEER_AUTHORISATION ); + ok = false; } - else - { - /* Neither supplied - server-authenticated TLS is the deliberate case. */ - } -} - -static inline bool MbedTlsStream_HasClientCredential(const struct SolidSyslogMbedTlsStreamConfig* config) -{ - return (config->ClientCertChain != NULL) && (config->ClientKey != NULL); + return ok; } -/* 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 MbedTlsStream_ClientKeyMatchesCertificate(const struct SolidSyslogMbedTlsStreamConfig* config) +/* A peer is authorised by a chain to trust anchors or by a pinned certificate + * fingerprint, and RFC 5425 4.2.1 makes the second sufficient on its own. With + * neither, there is nothing to check the peer against, so the connection stops + * rather than reaching a peer this stream cannot identify. */ +static inline bool MbedTlsStream_PeerIsAuthorisable(const struct SolidSyslogTlsCredentialsInstalled* installed) { - return mbedtls_pk_check_pair( - &config->ClientCertChain->pk, - config->ClientKey, - mbedtls_ctr_drbg_random, - config->Rng - ) == 0; + return installed->TrustAnchorsInstalled || (installed->FingerprintCount > 0U); } -/* 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 MbedTlsStream_HasHalfOfClientCredential(const struct SolidSyslogMbedTlsStreamConfig* config) +/* Answers every Install, so the integrator is always told when the credential + * window has closed - including on the paths where Open failed part way. */ +static inline void MbedTlsStream_ReleaseCredentials(struct SolidSyslogMbedTlsStream* self) { - return (config->ClientCertChain != NULL) != (config->ClientKey != NULL); + if (self->CredentialsInstalled) + { + self->CredentialsInstalled = false; + self->Config.Credentials->Release(self->Config.Credentials); + } } static inline bool MbedTlsStream_BindContextToConfig(struct SolidSyslogMbedTlsStream* self) diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamPrivate.h b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamPrivate.h index 2db5de6b..2ee7191a 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamPrivate.h +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamPrivate.h @@ -5,6 +5,7 @@ #ifndef SOLIDSYSLOGMBEDTLSSTREAMPRIVATE_H #define SOLIDSYSLOGMBEDTLSSTREAMPRIVATE_H +#include #include #include @@ -21,6 +22,11 @@ struct SolidSyslogMbedTlsStream struct SolidSyslogMbedTlsStreamConfig Config; mbedtls_ssl_config SslConfig; mbedtls_ssl_context SslContext; + /* Set immediately before Install is called, cleared by the Release that + * answers it. The contract is one Release per Install call whatever that + * call returned, and Close is idempotent, so the flag is what keeps both + * true at once. */ + bool CredentialsInstalled; }; void SolidSyslogMbedTlsStream_Initialise( diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamStatic.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamStatic.c index 62ddbc73..63054cf6 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamStatic.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamStatic.c @@ -88,6 +88,14 @@ static inline bool MbedTlsStream_IsValidConfig(const struct SolidSyslogMbedTlsSt SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NULL_RNG ); } + else if (config->Credentials == NULL) + { + MbedTlsStream_Report( + SOLIDSYSLOG_BAD_CONFIG_FATAL_SEVERITY, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NULL_CREDENTIALS + ); + } else { valid = true; diff --git a/Tests/MbedTls/CMakeLists.txt b/Tests/MbedTls/CMakeLists.txt index 832a21f7..633f20e0 100644 --- a/Tests/MbedTls/CMakeLists.txt +++ b/Tests/MbedTls/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(SolidSyslogMbedTlsStreamPoolTest main.cpp ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamStatic.c + ${CMAKE_SOURCE_DIR}/Tests/MbedTlsCredentialsFake.c ${CMAKE_SOURCE_DIR}/Tests/StreamFake.c ) @@ -52,6 +53,7 @@ add_executable(SolidSyslogMbedTlsStreamTest ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamStatic.c ${CMAKE_SOURCE_DIR}/Tests/AddressFake.c + ${CMAKE_SOURCE_DIR}/Tests/MbedTlsCredentialsFake.c ${CMAKE_SOURCE_DIR}/Tests/StreamFake.c ) diff --git a/Tests/MbedTls/SolidSyslogMbedTlsStreamPoolTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsStreamPoolTest.cpp index b5ef0190..9bf2626d 100644 --- a/Tests/MbedTls/SolidSyslogMbedTlsStreamPoolTest.cpp +++ b/Tests/MbedTls/SolidSyslogMbedTlsStreamPoolTest.cpp @@ -6,6 +6,8 @@ extern "C" #include "ConfigLockFake.h" #include "ErrorHandlerFake.h" +#include "MbedTlsCredentialsFake.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" #include "SolidSyslogMbedTlsStream.h" #include "SolidSyslogMbedTlsStreamErrors.h" #include "SolidSyslogNullStream.h" @@ -61,6 +63,8 @@ TEST_GROUP(SolidSyslogMbedTlsStreamPool) config.Transport = transport; config.Sleep = NoOpSleep; config.Rng = &rng; + MbedTlsCredentialsFake_Reset(); + config.Credentials = MbedTlsCredentialsFake_Get(); } void teardown() override @@ -184,6 +188,30 @@ TEST(SolidSyslogMbedTlsStreamPool, CreateWithNullRngReportsError) ); } +TEST(SolidSyslogMbedTlsStreamPool, CreateWithNullCredentialsReturnsFallback) +{ + config.Credentials = nullptr; + + struct SolidSyslogStream* fallback = SolidSyslogMbedTlsStream_Create(&config); + + CHECK_NULL_STREAM(fallback); +} + +TEST(SolidSyslogMbedTlsStreamPool, CreateWithNullCredentialsReportsError) +{ + ErrorHandlerFake_Install(nullptr); + config.Credentials = nullptr; + + SolidSyslogMbedTlsStream_Create(&config); + + CHECK_ERROR_REPORTED_ONCE( + SOLIDSYSLOG_SEVERITY_CRITICAL, + &SolidSyslogMbedTlsStreamErrorSource, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NULL_CREDENTIALS + ); +} + TEST(SolidSyslogMbedTlsStreamPool, CreateReturnsHandleDistinctFromFallback) { struct SolidSyslogStream* handle = SolidSyslogMbedTlsStream_Create(&config); diff --git a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp index c985e310..111b5b6c 100644 --- a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp +++ b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp @@ -9,7 +9,9 @@ extern "C" #include #include "ErrorHandlerFake.h" +#include "MbedTlsCredentialsFake.h" #include "MbedTlsFake.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" #include "SolidSyslogMbedTlsStream.h" #include "SolidSyslogMbedTlsStreamErrors.h" #include "SolidSyslogPrival.h" @@ -41,16 +43,14 @@ using namespace CososoTesting; #define CHECK_OPEN_UNWOUND_WITH_ERROR(transport, expectedCategory, expectedCode) \ CHECK_OPEN_UNWOUND_WITH_SEVERITY(transport, SOLIDSYSLOG_SEVERITY_ERROR, expectedCategory, expectedCode) -/* mTLS was asked for and is not in force, but the stream still connects - * server-authenticated - the degraded-but-delivering case docs/error-severity.md - * rates WARNING. */ -#define CHECK_INCOMPLETE_CREDENTIAL_REPORTED() \ - CHECK_ERROR_REPORTED_ONCE( \ - SOLIDSYSLOG_SEVERITY_WARNING, \ - &SolidSyslogMbedTlsStreamErrorSource, \ - SOLIDSYSLOG_CAT_BAD_CONFIG, \ - SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_CLIENT_CREDENTIAL_INCOMPLETE \ - ) +/* Records what mbedTLS had already done by the time the credentials were told + the window had closed. */ +static int SslConfigFreesSeenAtRelease; + +extern "C" void CaptureSslConfigFreesAtRelease(void) +{ + SslConfigFreesSeenAtRelease = MbedTlsFake_SslConfigFreeCallCount(); +} static int NoOpSleepCallCount; static int g_lastSleepMs; @@ -94,6 +94,7 @@ TEST_GROUP(SolidSyslogMbedTlsStream) void setup() override { MbedTlsFake_Reset(); + MbedTlsCredentialsFake_Reset(); ErrorHandlerFake_Install(nullptr); FakeGetHandshakeTimeoutMs_Reset(); NoOpSleepCallCount = 0; @@ -102,23 +103,11 @@ TEST_GROUP(SolidSyslogMbedTlsStream) config.Transport = transport; config.Sleep = NoOpSleep; config.Rng = &rng; + config.Credentials = MbedTlsCredentialsFake_Get(); handle = SolidSyslogMbedTlsStream_Create(&config); addr = AddressFake_Get(); } - /* Wires a client credential - either half may be null. ServerName is the - * explicit no-name-check opt-out, so the unverified-peer WARNING does not - * fire alongside and confuse the report count. Open is left to the caller: - * this recreates the handle, which resets the fakes, so anything arranged - * on them has to be set afterwards. */ - void WireClientCredential(mbedtls_x509_crt* cert, mbedtls_pk_context* key) - { - config.ClientCertChain = cert; - config.ClientKey = key; - config.ServerName = ""; - ReCreateHandleWithUpdatedConfig(); - } - /* Replaces the default Null-getter handle with one that uses the fake * handshake-timeout getter. Each test sets only the fake-getter return * value (or context) it needs different from the defaults restored in @@ -136,7 +125,7 @@ TEST_GROUP(SolidSyslogMbedTlsStream) StreamFake_Destroy(transport); } - /* Tests needing config tweaks (CaChain, Rng, ServerName, ...) call this + /* Tests needing config tweaks (Rng, ServerName, Credentials, ...) call this * to release setup()'s pool slot, mutate `config`, then re-Create. * Fully resets the fixture (transport, MbedTls fake counters, error * handler) so the test body observes counts from this Open onwards @@ -147,6 +136,7 @@ TEST_GROUP(SolidSyslogMbedTlsStream) SolidSyslogMbedTlsStream_Destroy(handle); StreamFake_Destroy(transport); MbedTlsFake_Reset(); + MbedTlsCredentialsFake_Reset(); ErrorHandlerFake_Install(nullptr); transport = StreamFake_Create(); config.Transport = transport; @@ -830,21 +820,6 @@ TEST(SolidSyslogMbedTlsStream, OpenPinsMinimumTlsVersionToTls12) LONGS_EQUAL(MBEDTLS_SSL_VERSION_TLS1_2, MbedTlsFake_ConfMinTlsVersion(MbedTlsFake_LastSslConfigInitArg())); } -TEST(SolidSyslogMbedTlsStream, OpenWiresCaChainFromConfigAndNullCrl) - -{ - /* Use a non-null marker pointer; the fake captures it without dereferencing. */ - static mbedtls_x509_crt caChainMarker; - config.CaChain = &caChainMarker; - ReCreateHandleWithUpdatedConfig(); - SolidSyslogStream_Open(handle, addr); - - LONGS_EQUAL(1, MbedTlsFake_SslConfCaChainCallCount()); - POINTERS_EQUAL(MbedTlsFake_LastSslConfigInitArg(), MbedTlsFake_LastSslConfCaChainConfigArg()); - POINTERS_EQUAL(&caChainMarker, MbedTlsFake_LastSslConfCaChainArg()); - POINTERS_EQUAL(nullptr, MbedTlsFake_LastSslConfCaChainCrlArg()); -} - TEST(SolidSyslogMbedTlsStream, OpenWiresRngFromConfigUsingCtrDrbgRandom) { @@ -935,148 +910,157 @@ TEST(SolidSyslogMbedTlsStream, OpenConnectsWhenServerNameIsEmpty) } /* ------------------------------------------------------------------------- - * mTLS client identity wiring. When the integrator supplies both a - * ClientCertChain and a ClientKey, Open must call mbedtls_ssl_conf_own_cert - * so the client presents its cert during the handshake. Either pointer - * being NULL means "server-auth only" - skip the wiring. + * Credentials. The stream holds no material of its own: it asks its + * credentials source to install onto the ssl_config once per connection, and + * tells it when that connection ends. * ------------------------------------------------------------------------- */ -TEST(SolidSyslogMbedTlsStream, OpenWiresOwnCertWhenClientCertAndKeyProvided) - +TEST(SolidSyslogMbedTlsStream, OpenAsksTheCredentialsToInstallOntoItsSslConfig) { - static mbedtls_x509_crt clientCertMarker; - static mbedtls_pk_context clientKeyMarker; - - WireClientCredential(&clientCertMarker, &clientKeyMarker); SolidSyslogStream_Open(handle, addr); - LONGS_EQUAL(1, MbedTlsFake_SslConfOwnCertCallCount()); - POINTERS_EQUAL(MbedTlsFake_LastSslConfigInitArg(), MbedTlsFake_LastSslConfOwnCertConfigArg()); - POINTERS_EQUAL(&clientCertMarker, MbedTlsFake_LastSslConfOwnCertCertArg()); - POINTERS_EQUAL(&clientKeyMarker, MbedTlsFake_LastSslConfOwnCertKeyArg()); + LONGS_EQUAL(1, MbedTlsCredentialsFake_InstallCallCount()); + POINTERS_EQUAL(MbedTlsFake_LastSslConfigInitArg(), MbedTlsCredentialsFake_LastInstallConfig()); } -TEST(SolidSyslogMbedTlsStream, OpenSkipsOwnCertWhenClientCertChainIsNull) - +TEST(SolidSyslogMbedTlsStream, OpenInstallsCredentialsBeforeTheHandshake) { - static mbedtls_pk_context clientKeyMarker; + ArrangePersistentHandshakeError(MBEDTLS_ERR_SSL_BAD_INPUT_DATA); - WireClientCredential(nullptr, &clientKeyMarker); SolidSyslogStream_Open(handle, addr); - LONGS_EQUAL(0, MbedTlsFake_SslConfOwnCertCallCount()); + LONGS_EQUAL(1, MbedTlsCredentialsFake_InstallCallCount()); } -TEST(SolidSyslogMbedTlsStream, OpenSkipsOwnCertWhenClientKeyIsNull) +TEST(SolidSyslogMbedTlsStream, OpenFailsWhenTheCredentialsCannotInstall) +{ + MbedTlsCredentialsFake_SetInstallSucceeds(false); + + CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); +} +TEST(SolidSyslogMbedTlsStream, OpenClosesTransportAndFreesSslStateWhenTheCredentialsCannotInstall) { - static mbedtls_x509_crt clientCertMarker; + MbedTlsCredentialsFake_SetInstallSucceeds(false); - WireClientCredential(&clientCertMarker, nullptr); SolidSyslogStream_Open(handle, addr); - LONGS_EQUAL(0, MbedTlsFake_SslConfOwnCertCallCount()); + LONGS_EQUAL(1, StreamFake_CloseCallCount(transport)); + LONGS_EQUAL(1, MbedTlsFake_SslFreeCallCount()); + LONGS_EQUAL(1, MbedTlsFake_SslConfigFreeCallCount()); } -TEST(SolidSyslogMbedTlsStream, OpenReportsIncompleteClientCredentialWhenClientKeyIsNull) - +TEST(SolidSyslogMbedTlsStream, OpenDoesNotHandshakeWhenTheCredentialsCannotInstall) { - static mbedtls_x509_crt clientCertMarker; + MbedTlsCredentialsFake_SetInstallSucceeds(false); - WireClientCredential(&clientCertMarker, nullptr); SolidSyslogStream_Open(handle, addr); - CHECK_INCOMPLETE_CREDENTIAL_REPORTED(); + LONGS_EQUAL(0, MbedTlsFake_SslHandshakeCallCount()); } -TEST(SolidSyslogMbedTlsStream, OpenReportsIncompleteClientCredentialWhenClientCertChainIsNull) +/* Nothing vouches for the peer and nothing pins it, so there is no check the + * handshake could fail - the connection stops rather than reaching a collector + * this stream cannot identify. */ +TEST(SolidSyslogMbedTlsStream, OpenFailsWhenNothingAuthorisesThePeer) +{ + MbedTlsCredentialsFake_SetTrustAnchorsInstalled(false); + + CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); +} +TEST(SolidSyslogMbedTlsStream, OpenReportsThatNothingAuthorisesThePeer) { - static mbedtls_pk_context clientKeyMarker; + config.ServerName = ""; + ReCreateHandleWithUpdatedConfig(); + MbedTlsCredentialsFake_SetTrustAnchorsInstalled(false); - WireClientCredential(nullptr, &clientKeyMarker); SolidSyslogStream_Open(handle, addr); - CHECK_INCOMPLETE_CREDENTIAL_REPORTED(); + CHECK_OPEN_UNWOUND_WITH_ERROR( + transport, + SOLIDSYSLOG_CAT_BAD_CONFIG, + SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NO_PEER_AUTHORISATION + ); } -TEST(SolidSyslogMbedTlsStream, OpenReportsClientCredentialNotInstalledAndStillConnects) +/* RFC 5425 4.2.1 makes a pinned certificate fingerprint sufficient on its own, + * so a peer with no trust anchors behind it is still authorisable. */ +TEST(SolidSyslogMbedTlsStream, OpenConnectsWhenOnlyAFingerprintAuthorisesThePeer) { - /* Both halves supplied, but mbedTLS cannot take them - the only documented - * failure is MBEDTLS_ERR_SSL_ALLOC_FAILED, which returns before anything is - * appended to the config. Nothing is presented, so the connection continues - * server-authenticated and the collector decides whether to accept it. */ - static mbedtls_x509_crt clientCertMarker; - static mbedtls_pk_context clientKeyMarker; - WireClientCredential(&clientCertMarker, &clientKeyMarker); - MbedTlsFake_SetSslConfOwnCertReturn(MBEDTLS_ERR_SSL_ALLOC_FAILED); + static const char* const pins[] = {"sha-256:AA"}; + MbedTlsCredentialsFake_SetTrustAnchorsInstalled(false); + MbedTlsCredentialsFake_SetFingerprints(pins, 1); CHECK_TRUE(SolidSyslogStream_Open(handle, addr)); - - CHECK_ERROR_REPORTED_ONCE( - SOLIDSYSLOG_SEVERITY_WARNING, - &SolidSyslogMbedTlsStreamErrorSource, - SOLIDSYSLOG_CAT_BAD_CONFIG, - SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_CLIENT_CREDENTIAL_NOT_INSTALLED - ); } -/* ------------------------------------------------------------------------- - * mTLS credential pairing. mbedtls_pk_check_pair compares the certificate's - * public key with the private key locally, so a mismatched pair is caught on - * the device instead of surfacing as a rejection from the collector. - * ------------------------------------------------------------------------- */ +TEST(SolidSyslogMbedTlsStream, CloseReleasesTheCredentialsItInstalled) +{ + SolidSyslogStream_Open(handle, addr); -TEST(SolidSyslogMbedTlsStream, OpenChecksClientKeyAgainstItsCertificate) + SolidSyslogStream_Close(handle); + LONGS_EQUAL(1, MbedTlsCredentialsFake_ReleaseCallCount()); +} + +/* One Release per Install call, whatever that call returned - which is what + * spares every backend a rollback path of its own. */ +TEST(SolidSyslogMbedTlsStream, AFailedInstallIsStillAnsweredByARelease) { - static mbedtls_x509_crt clientCertMarker; - static mbedtls_pk_context clientKeyMarker; - static mbedtls_ctr_drbg_context rngMarker; + MbedTlsCredentialsFake_SetInstallSucceeds(false); - config.Rng = &rngMarker; - WireClientCredential(&clientCertMarker, &clientKeyMarker); SolidSyslogStream_Open(handle, addr); - LONGS_EQUAL(1, MbedTlsFake_PkCheckPairCallCount()); - POINTERS_EQUAL(&clientCertMarker.pk, MbedTlsFake_LastPkCheckPairPublicKeyArg()); - POINTERS_EQUAL(&clientKeyMarker, MbedTlsFake_LastPkCheckPairPrivateKeyArg()); - POINTERS_EQUAL((void*) mbedtls_ctr_drbg_random, (void*) MbedTlsFake_LastPkCheckPairRngFuncArg()); - POINTERS_EQUAL(&rngMarker, MbedTlsFake_LastPkCheckPairRngContextArg()); + LONGS_EQUAL(1, MbedTlsCredentialsFake_ReleaseCallCount()); } -TEST(SolidSyslogMbedTlsStream, OpenReportsMismatchedClientCredentialAndStillConnects) - +TEST(SolidSyslogMbedTlsStream, AnOpenThatFailedLaterIsStillAnsweredByARelease) { - /* The pair is refused locally, so nothing is presented and the connection - * continues server-authenticated - the collector decides whether to accept - * it, as for every other fault in the credential we offer. */ - static mbedtls_x509_crt clientCertMarker; - static mbedtls_pk_context clientKeyMarker; + ArrangePersistentHandshakeError(MBEDTLS_ERR_SSL_BAD_INPUT_DATA); - WireClientCredential(&clientCertMarker, &clientKeyMarker); - MbedTlsFake_SetPkCheckPairReturn(MBEDTLS_ERR_PK_TYPE_MISMATCH); + SolidSyslogStream_Open(handle, addr); - CHECK_TRUE(SolidSyslogStream_Open(handle, addr)); + LONGS_EQUAL(1, MbedTlsCredentialsFake_ReleaseCallCount()); +} - CHECK_ERROR_REPORTED_ONCE( - SOLIDSYSLOG_SEVERITY_WARNING, - &SolidSyslogMbedTlsStreamErrorSource, - SOLIDSYSLOG_CAT_BAD_CONFIG, - SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_CLIENT_CREDENTIAL_MISMATCHED - ); +TEST(SolidSyslogMbedTlsStream, CloseWithoutAnOpenReleasesNothing) +{ + SolidSyslogStream_Close(handle); + + LONGS_EQUAL(0, MbedTlsCredentialsFake_ReleaseCallCount()); } -TEST(SolidSyslogMbedTlsStream, OpenSkipsOwnCertWhenClientKeyDoesNotMatchCertificate) +TEST(SolidSyslogMbedTlsStream, CloseTwiceReleasesOnlyOnce) +{ + SolidSyslogStream_Open(handle, addr); + + SolidSyslogStream_Close(handle); + SolidSyslogStream_Close(handle); + LONGS_EQUAL(1, MbedTlsCredentialsFake_ReleaseCallCount()); +} + +/* The ssl_config holds pointers into the caller's certificates until it is + * freed, so the credentials are told the window has closed only once mbedTLS + * has let go of them. */ +TEST(SolidSyslogMbedTlsStream, CredentialsAreReleasedAfterTheSslConfigIsFreed) { - static mbedtls_x509_crt clientCertMarker; - static mbedtls_pk_context clientKeyMarker; + SslConfigFreesSeenAtRelease = 0; + MbedTlsCredentialsFake_SetReleaseObserver(CaptureSslConfigFreesAtRelease); + SolidSyslogStream_Open(handle, addr); - WireClientCredential(&clientCertMarker, &clientKeyMarker); - MbedTlsFake_SetPkCheckPairReturn(MBEDTLS_ERR_PK_TYPE_MISMATCH); + SolidSyslogStream_Close(handle); + + LONGS_EQUAL(1, SslConfigFreesSeenAtRelease); +} + +TEST(SolidSyslogMbedTlsStream, ASecondOpenInstallsTheCredentialsAgain) +{ + SolidSyslogStream_Open(handle, addr); + SolidSyslogStream_Close(handle); SolidSyslogStream_Open(handle, addr); - LONGS_EQUAL(0, MbedTlsFake_SslConfOwnCertCallCount()); + LONGS_EQUAL(2, MbedTlsCredentialsFake_InstallCallCount()); } diff --git a/Tests/MbedTlsCredentialsFake.c b/Tests/MbedTlsCredentialsFake.c new file mode 100644 index 00000000..039e2072 --- /dev/null +++ b/Tests/MbedTlsCredentialsFake.c @@ -0,0 +1,101 @@ +#include "MbedTlsCredentialsFake.h" + +#include + +#include "SolidSyslogMbedTlsCredentialsDefinition.h" +#include "SolidSyslogTlsCredentialsInstalled.h" + +struct MbedTlsCredentialsFake +{ + struct SolidSyslogMbedTlsCredentials Base; + int InstallCallCount; + struct mbedtls_ssl_config* LastInstallConfig; + int ReleaseCallCount; + bool InstallSucceeds; + bool TrustAnchorsInstalled; + const char* const * Fingerprints; + size_t FingerprintCount; + void (*ReleaseObserver)(void); +}; + +static struct MbedTlsCredentialsFake fake; + +static bool Install( + struct SolidSyslogMbedTlsCredentials* self, + struct mbedtls_ssl_config* conf, + struct SolidSyslogTlsCredentialsInstalled* installed +) +{ + (void) self; + fake.InstallCallCount++; + fake.LastInstallConfig = conf; + installed->TrustAnchorsInstalled = fake.TrustAnchorsInstalled; + installed->Fingerprints = fake.Fingerprints; + installed->FingerprintCount = fake.FingerprintCount; + return fake.InstallSucceeds; +} + +static void Release(struct SolidSyslogMbedTlsCredentials* self) +{ + (void) self; + fake.ReleaseCallCount++; + if (fake.ReleaseObserver != NULL) + { + fake.ReleaseObserver(); + } +} + +struct SolidSyslogMbedTlsCredentials* MbedTlsCredentialsFake_Get(void) +{ + return &fake.Base; +} + +void MbedTlsCredentialsFake_Reset(void) +{ + fake.Base.Install = Install; + fake.Base.Release = Release; + fake.InstallCallCount = 0; + fake.LastInstallConfig = NULL; + fake.ReleaseCallCount = 0; + fake.InstallSucceeds = true; + fake.TrustAnchorsInstalled = true; + fake.Fingerprints = NULL; + fake.FingerprintCount = 0U; + fake.ReleaseObserver = NULL; +} + +int MbedTlsCredentialsFake_InstallCallCount(void) +{ + return fake.InstallCallCount; +} + +struct mbedtls_ssl_config* MbedTlsCredentialsFake_LastInstallConfig(void) +{ + return fake.LastInstallConfig; +} + +int MbedTlsCredentialsFake_ReleaseCallCount(void) +{ + return fake.ReleaseCallCount; +} + +void MbedTlsCredentialsFake_SetReleaseObserver(void (*observer)(void)) +{ + fake.ReleaseObserver = observer; +} + +void MbedTlsCredentialsFake_SetInstallSucceeds(bool succeeds) +{ + fake.InstallSucceeds = succeeds; +} + +void MbedTlsCredentialsFake_SetTrustAnchorsInstalled(bool installed) +{ + fake.TrustAnchorsInstalled = installed; +} + +void MbedTlsCredentialsFake_SetFingerprints(const char* const * fingerprints, size_t count) +{ + fake.Fingerprints = fingerprints; + fake.FingerprintCount = count; +} diff --git a/Tests/MbedTlsCredentialsFake.h b/Tests/MbedTlsCredentialsFake.h new file mode 100644 index 00000000..312ca9aa --- /dev/null +++ b/Tests/MbedTlsCredentialsFake.h @@ -0,0 +1,36 @@ +#ifndef MBEDTLSCREDENTIALSFAKE_H +#define MBEDTLSCREDENTIALSFAKE_H + +#include +#include + +#include "SolidSyslogExternC.h" + +struct mbedtls_ssl_config; + +SOLIDSYSLOG_EXTERN_C_BEGIN + + struct SolidSyslogMbedTlsCredentials; + + /* A credentials double for the MbedTlsStream tests: records the calls the + stream makes, and lets a test dictate what Install reports back. Backed + by a single static instance, so Reset is what separates one test from + the next - there is nothing to destroy. */ + struct SolidSyslogMbedTlsCredentials* MbedTlsCredentialsFake_Get(void); + void MbedTlsCredentialsFake_Reset(void); + + int MbedTlsCredentialsFake_InstallCallCount(void); + struct mbedtls_ssl_config* MbedTlsCredentialsFake_LastInstallConfig(void); + int MbedTlsCredentialsFake_ReleaseCallCount(void); + + /* Called from inside Release, so a test can observe what has already + happened at the moment the credential window closes. */ + void MbedTlsCredentialsFake_SetReleaseObserver(void (*observer)(void)); + + void MbedTlsCredentialsFake_SetInstallSucceeds(bool succeeds); + void MbedTlsCredentialsFake_SetTrustAnchorsInstalled(bool installed); + void MbedTlsCredentialsFake_SetFingerprints(const char* const * fingerprints, size_t count); + +SOLIDSYSLOG_EXTERN_C_END + +#endif /* MBEDTLSCREDENTIALSFAKE_H */ From d35d64a769bdd7fba6cae1009173551f24aea938 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 1 Sep 2026 20:50:35 +0100 Subject: [PATCH 3/4] feat: wire the Mbed TLS BDD targets and integration suite to the credentials role --- .../BddTargetTlsSender_MbedTls_LwipRawTcp.c | 14 +++- .../BddTargetTlsSender_MbedTls_PlusTcpTcp.c | 14 +++- Tests/MbedTlsIntegration/CMakeLists.txt | 3 + ...olidSyslogMbedTlsStreamIntegrationTest.cpp | 76 ++++++++++++------- 4 files changed, 74 insertions(+), 33 deletions(-) diff --git a/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_LwipRawTcp.c b/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_LwipRawTcp.c index 6ce13ccf..b6232082 100644 --- a/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_LwipRawTcp.c +++ b/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_LwipRawTcp.c @@ -28,6 +28,7 @@ #include "BddTargetTlsConfig.h" #include "SolidSyslogLwipRawAddress.h" #include "SolidSyslogLwipRawTcpStream.h" +#include "SolidSyslogMbedTlsHandleCredentials.h" #include "SolidSyslogMbedTlsStream.h" #include "SolidSyslogNullSender.h" #include "SolidSyslogStream.h" @@ -56,6 +57,7 @@ struct SolidSyslogResolver; static struct SolidSyslogStream* underlyingStream; +static struct SolidSyslogMbedTlsCredentials* credentials; static struct SolidSyslogStream* tlsStream; static struct SolidSyslogAddress* address; static struct SolidSyslogSender* sender; @@ -378,13 +380,18 @@ struct SolidSyslogSender* BddTargetTlsSender_Create(struct SolidSyslogResolver* tlsStreamConfig.Transport = underlyingStream; tlsStreamConfig.Sleep = RtosSleep; tlsStreamConfig.Rng = &drbg; - tlsStreamConfig.CaChain = &caChain; /* Plain-TLS and mTLS share one SNI on this oracle (CN/SAN = "syslog-ng"), * so BddTargetTlsConfig_GetServerName and BddTargetMtlsConfig_GetServerName * return the same string. Use the TLS one to make the equivalence explicit. */ tlsStreamConfig.ServerName = BddTargetTlsConfig_GetServerName(); - tlsStreamConfig.ClientCertChain = &clientCertChain; - tlsStreamConfig.ClientKey = &clientKey; + static struct SolidSyslogMbedTlsHandleCredentialsConfig credentialsConfig; + credentialsConfig = (struct SolidSyslogMbedTlsHandleCredentialsConfig) {0}; + credentialsConfig.Rng = &drbg; + credentialsConfig.CaChain = &caChain; + credentialsConfig.ClientCertChain = &clientCertChain; + credentialsConfig.ClientKey = &clientKey; + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&credentialsConfig); + tlsStreamConfig.Credentials = credentials; tlsStream = SolidSyslogMbedTlsStream_Create(&tlsStreamConfig); address = SolidSyslogLwipRawAddress_Create(); @@ -414,6 +421,7 @@ void BddTargetTlsSender_Destroy(void) SolidSyslogStreamSender_Destroy(sender); SolidSyslogLwipRawAddress_Destroy(address); SolidSyslogMbedTlsStream_Destroy(tlsStream); + SolidSyslogMbedTlsHandleCredentials_Destroy(credentials); SolidSyslogLwipRawTcpStream_Destroy(underlyingStream); /* Entropy / DRBG / parsed certs survive across Destroy -> Create cycles to diff --git a/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c b/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c index 0645d988..f2f82cfa 100644 --- a/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c +++ b/Bdd/Targets/Common/BddTargetTlsSender_MbedTls_PlusTcpTcp.c @@ -27,6 +27,7 @@ #include "BddTargetTlsConfig.h" #include "SolidSyslogPlusTcpAddress.h" #include "SolidSyslogPlusTcpTcpStream.h" +#include "SolidSyslogMbedTlsHandleCredentials.h" #include "SolidSyslogMbedTlsStream.h" #include "SolidSyslogNullSender.h" #include "SolidSyslogStream.h" @@ -55,6 +56,7 @@ struct SolidSyslogResolver; static struct SolidSyslogStream* underlyingStream; +static struct SolidSyslogMbedTlsCredentials* credentials; static struct SolidSyslogStream* tlsStream; static struct SolidSyslogAddress* address; static struct SolidSyslogSender* sender; @@ -367,13 +369,18 @@ struct SolidSyslogSender* BddTargetTlsSender_Create(struct SolidSyslogResolver* tlsStreamConfig.Transport = underlyingStream; tlsStreamConfig.Sleep = RtosSleep; tlsStreamConfig.Rng = &drbg; - tlsStreamConfig.CaChain = &caChain; /* Plain-TLS and mTLS share one SNI on this oracle (CN/SAN = "syslog-ng"), * so BddTargetTlsConfig_GetServerName and BddTargetMtlsConfig_GetServerName * return the same string. Use the TLS one to make the equivalence explicit. */ tlsStreamConfig.ServerName = BddTargetTlsConfig_GetServerName(); - tlsStreamConfig.ClientCertChain = &clientCertChain; - tlsStreamConfig.ClientKey = &clientKey; + static struct SolidSyslogMbedTlsHandleCredentialsConfig credentialsConfig; + credentialsConfig = (struct SolidSyslogMbedTlsHandleCredentialsConfig) {0}; + credentialsConfig.Rng = &drbg; + credentialsConfig.CaChain = &caChain; + credentialsConfig.ClientCertChain = &clientCertChain; + credentialsConfig.ClientKey = &clientKey; + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&credentialsConfig); + tlsStreamConfig.Credentials = credentials; tlsStream = SolidSyslogMbedTlsStream_Create(&tlsStreamConfig); address = SolidSyslogPlusTcpAddress_Create(); @@ -403,6 +410,7 @@ void BddTargetTlsSender_Destroy(void) SolidSyslogStreamSender_Destroy(sender); SolidSyslogPlusTcpAddress_Destroy(address); SolidSyslogMbedTlsStream_Destroy(tlsStream); + SolidSyslogMbedTlsHandleCredentials_Destroy(credentials); SolidSyslogPlusTcpTcpStream_Destroy(underlyingStream); /* Entropy / DRBG / parsed certs survive across Destroy -> Create cycles to diff --git a/Tests/MbedTlsIntegration/CMakeLists.txt b/Tests/MbedTlsIntegration/CMakeLists.txt index a2d0e0d8..098e6fc0 100644 --- a/Tests/MbedTlsIntegration/CMakeLists.txt +++ b/Tests/MbedTlsIntegration/CMakeLists.txt @@ -55,6 +55,9 @@ 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/SolidSyslogMbedTlsHandleCredentials.c + ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c + ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsNullCredentials.c ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicy.c ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicyStatic.c ) diff --git a/Tests/MbedTlsIntegration/SolidSyslogMbedTlsStreamIntegrationTest.cpp b/Tests/MbedTlsIntegration/SolidSyslogMbedTlsStreamIntegrationTest.cpp index 1e1cb7c3..d34e1804 100644 --- a/Tests/MbedTlsIntegration/SolidSyslogMbedTlsStreamIntegrationTest.cpp +++ b/Tests/MbedTlsIntegration/SolidSyslogMbedTlsStreamIntegrationTest.cpp @@ -12,6 +12,8 @@ extern "C" #include "MbedTlsTestServer.h" #include "SocketStream.h" #include "SolidSyslogError.h" +#include "SolidSyslogMbedTlsCredentialsDefinition.h" +#include "SolidSyslogMbedTlsHandleCredentials.h" #include "SolidSyslogMbedTlsStream.h" #include "SolidSyslogMbedTlsStreamErrors.h" #include "SolidSyslogPrival.h" @@ -69,6 +71,8 @@ TEST_GROUP(SolidSyslogMbedTlsStreamIntegration) struct MbedTlsTestServer* server = nullptr; struct SolidSyslogStream* clientTransport = nullptr; struct SolidSyslogStream* tlsStream = nullptr; + struct SolidSyslogMbedTlsHandleCredentialsConfig credsConfig = {}; + struct SolidSyslogMbedTlsCredentials* credentials = nullptr; struct SolidSyslogAddress* addr = nullptr; void setup() override @@ -113,6 +117,10 @@ TEST_GROUP(SolidSyslogMbedTlsStreamIntegration) { SolidSyslogMbedTlsStream_Destroy(tlsStream); } + if (credentials != nullptr) + { + SolidSyslogMbedTlsHandleCredentials_Destroy(credentials); + } if (clientTransport != nullptr) { SocketStream_Destroy(clientTransport); @@ -180,21 +188,32 @@ TEST_GROUP(SolidSyslogMbedTlsStreamIntegration) MbedTlsTestCert_Create(&certConfig, outCert, &rng); } - /* Common config wiring used by every integration test: transport, sleep, - * fixture-owned DRBG, and the trusted-CA / hostname pair built in setup(). - * Per-test tweaks (e.g. swapping the CA chain to test rejection, or - * adding ClientCertChain + ClientKey for mTLS) overlay onto the returned - * struct before passing it to SolidSyslogMbedTlsStream_Create. */ + /* Common wiring used by every integration test: transport, sleep, + * fixture-owned DRBG, and the hostname built in setup(). The material - + * trust anchors here, plus ClientCertChain / ClientKey where a test wants + * mTLS - goes on credsConfig, which CreateTlsStream turns into the + * credentials the stream asks at Open. Per-test tweaks overlay onto either + * struct before CreateTlsStream. */ struct SolidSyslogMbedTlsStreamConfig BuildBaseConfig(struct SolidSyslogStream* transport) { + credsConfig = {}; + credsConfig.Rng = &rng; + credsConfig.CaChain = &trustedCa.Cert; + struct SolidSyslogMbedTlsStreamConfig cfg = {}; cfg.Transport = transport; cfg.Sleep = NoOpSleep; cfg.Rng = &rng; - cfg.CaChain = &trustedCa.Cert; cfg.ServerName = TEST_SERVER_HOSTNAME; return cfg; } + + struct SolidSyslogStream* CreateTlsStream(struct SolidSyslogMbedTlsStreamConfig* cfg) + { + credentials = SolidSyslogMbedTlsHandleCredentials_Create(&credsConfig); + cfg->Credentials = credentials; + return SolidSyslogMbedTlsStream_Create(cfg); + } }; // clang-format on @@ -204,7 +223,7 @@ TEST(SolidSyslogMbedTlsStreamIntegration, HandshakeSucceedsWhenServerCertSignedB { struct SolidSyslogStream* transport = StartServerWithCert(&serverCert); struct SolidSyslogMbedTlsStreamConfig config = BuildBaseConfig(transport); - tlsStream = SolidSyslogMbedTlsStream_Create(&config); + tlsStream = CreateTlsStream(&config); bool opened = SolidSyslogStream_Open(tlsStream, addr); @@ -228,8 +247,8 @@ TEST(SolidSyslogMbedTlsStreamIntegration, HandshakeFailsWhenServerCertSignedByUn struct SolidSyslogStream* transport = StartServerWithCert(&serverCert); struct SolidSyslogMbedTlsStreamConfig config = BuildBaseConfig(transport); - config.CaChain = &untrustedCa.Cert; - tlsStream = SolidSyslogMbedTlsStream_Create(&config); + credsConfig.CaChain = &untrustedCa.Cert; + tlsStream = CreateTlsStream(&config); bool opened = SolidSyslogStream_Open(tlsStream, addr); @@ -245,7 +264,7 @@ TEST(SolidSyslogMbedTlsStreamIntegration, HandshakeFailsWhenServerNameDoesNotMat struct SolidSyslogStream* transport = StartServerWithCert(&serverCert); struct SolidSyslogMbedTlsStreamConfig config = BuildBaseConfig(transport); config.ServerName = "wrong-host.example.com"; /* server cert has SAN syslog.example.com */ - tlsStream = SolidSyslogMbedTlsStream_Create(&config); + tlsStream = CreateTlsStream(&config); bool opened = SolidSyslogStream_Open(tlsStream, addr); @@ -253,20 +272,23 @@ TEST(SolidSyslogMbedTlsStreamIntegration, HandshakeFailsWhenServerNameDoesNotMat CHECK_REFUSAL_REPORTED(SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_PEER_NAME_MISMATCHED); } -/* No trust anchors at all. mbedtls_ssl_conf_ca_chain takes the NULL without - * complaint, so the fault only surfaces when the peer certificate finds no - * parent to chain to - an untrusted peer, which is not the same diagnosis as - * the anchors never having been configured. #753 covers that gap. */ -TEST(SolidSyslogMbedTlsStreamIntegration, HandshakeFailsAsUntrustedWhenNoTrustAnchorsAreConfigured) +/* No trust anchors and no pinned fingerprint: nothing authorises the peer, so + * the connection stops before the handshake rather than reaching a collector + * this stream cannot identify. */ +TEST(SolidSyslogMbedTlsStreamIntegration, OpenFailsWhenNothingAuthorisesThePeer) { struct SolidSyslogStream* transport = StartServerWithCert(&serverCert); struct SolidSyslogMbedTlsStreamConfig config = BuildBaseConfig(transport); - config.CaChain = nullptr; - tlsStream = SolidSyslogMbedTlsStream_Create(&config); + credsConfig.CaChain = nullptr; + tlsStream = CreateTlsStream(&config); CHECK_FALSE(SolidSyslogStream_Open(tlsStream, addr)); - CHECK_REFUSAL_REPORTED(SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_PEER_CERTIFICATE_UNTRUSTED); + LONGS_EQUAL(1, CapturedErrorCount); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, LastCapturedError.Severity); + POINTERS_EQUAL(&SolidSyslogMbedTlsStreamErrorSource, LastCapturedError.Source); + UNSIGNED_LONGS_EQUAL(SOLIDSYSLOG_CAT_BAD_CONFIG, LastCapturedError.Category); + LONGS_EQUAL(SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_NO_PEER_AUTHORISATION, LastCapturedError.Detail); } TEST(SolidSyslogMbedTlsStreamIntegration, HandshakeFailsWhenServerCertHasExpired) @@ -277,7 +299,7 @@ TEST(SolidSyslogMbedTlsStreamIntegration, HandshakeFailsWhenServerCertHasExpired struct SolidSyslogStream* transport = StartServerWithCert(&expiredCert); struct SolidSyslogMbedTlsStreamConfig config = BuildBaseConfig(transport); - tlsStream = SolidSyslogMbedTlsStream_Create(&config); + tlsStream = CreateTlsStream(&config); CHECK_FALSE(SolidSyslogStream_Open(tlsStream, addr)); CHECK_REFUSAL_REPORTED(SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_PEER_CERTIFICATE_EXPIRED); @@ -293,7 +315,7 @@ TEST(SolidSyslogMbedTlsStreamIntegration, HandshakeFailsWhenServerCertIsNotYetVa struct SolidSyslogStream* transport = StartServerWithCert(&futureCert); struct SolidSyslogMbedTlsStreamConfig config = BuildBaseConfig(transport); - tlsStream = SolidSyslogMbedTlsStream_Create(&config); + tlsStream = CreateTlsStream(&config); CHECK_FALSE(SolidSyslogStream_Open(tlsStream, addr)); CHECK_REFUSAL_REPORTED(SOLIDSYSLOG_MBEDTLS_STREAM_ERROR_PEER_CERTIFICATE_NOT_YET_VALID); @@ -316,9 +338,9 @@ TEST(SolidSyslogMbedTlsStreamIntegration, MutualTlsHandshakeSucceedsWithClientCe struct SolidSyslogStream* transport = StartServerRequiringClientCa(&serverCert, &clientCa); struct SolidSyslogMbedTlsStreamConfig config = BuildBaseConfig(transport); - config.ClientCertChain = &clientCert.Cert; - config.ClientKey = &clientCert.Key; - tlsStream = SolidSyslogMbedTlsStream_Create(&config); + credsConfig.ClientCertChain = &clientCert.Cert; + credsConfig.ClientKey = &clientCert.Key; + tlsStream = CreateTlsStream(&config); bool opened = SolidSyslogStream_Open(tlsStream, addr); @@ -346,7 +368,7 @@ TEST(SolidSyslogMbedTlsStreamIntegration, MutualTlsHandshakeRejectedWhenClientSe struct SolidSyslogStream* transport = StartServerRequiringClientCa(&serverCert, &clientCa); struct SolidSyslogMbedTlsStreamConfig config = BuildBaseConfig(transport); - tlsStream = SolidSyslogMbedTlsStream_Create(&config); + tlsStream = CreateTlsStream(&config); bool opened = SolidSyslogStream_Open(tlsStream, addr); @@ -379,9 +401,9 @@ TEST(SolidSyslogMbedTlsStreamIntegration, MutualTlsHandshakeRejectedWhenClientCe struct SolidSyslogStream* transport = StartServerRequiringClientCa(&serverCert, &trustedClientCa); struct SolidSyslogMbedTlsStreamConfig config = BuildBaseConfig(transport); - config.ClientCertChain = &clientCert.Cert; - config.ClientKey = &clientCert.Key; - tlsStream = SolidSyslogMbedTlsStream_Create(&config); + credsConfig.ClientCertChain = &clientCert.Cert; + credsConfig.ClientKey = &clientCert.Key; + tlsStream = CreateTlsStream(&config); bool opened = SolidSyslogStream_Open(tlsStream, addr); From bf9b4eda952b8e65625a283c8c1ba013525bc4b4 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 1 Sep 2026 20:54:44 +0100 Subject: [PATCH 4/4] docs: the Mbed TLS pack asks a credentials source for its material --- ...olidSyslogMbedTlsHandleCredentialsStatic.c | 9 +-- docs/generated/MbedTls-manifest.txt | 2 + docs/generated/beta-stack-manifest.txt | 2 + docs/hardening-path.md | 26 +++++--- docs/platforms/mbedtls/index.md | 64 ++++++++----------- docs/platforms/mbedtls/setup.md | 38 ++++++++--- misra_suppressions.txt | 13 ++-- 7 files changed, 85 insertions(+), 69 deletions(-) diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c index 6541b743..0123a742 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c @@ -16,15 +16,13 @@ #include "SolidSyslogPrival.h" #include "SolidSyslogTunables.h" -static inline bool MbedTlsHandleCredentials_IsValidConfig( - const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +static inline bool MbedTlsHandleCredentials_IsValidConfig(const struct SolidSyslogMbedTlsHandleCredentialsConfig* config ); static inline size_t MbedTlsHandleCredentials_IndexFromHandle(const struct SolidSyslogMbedTlsCredentials* base); static inline void MbedTlsHandleCredentials_CleanupAtIndex(size_t index, void* context); static bool MbedTlsHandleCredentials_InUse[SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE]; -static struct SolidSyslogMbedTlsHandleCredentials - MbedTlsHandleCredentials_Pool[SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE]; +static struct SolidSyslogMbedTlsHandleCredentials MbedTlsHandleCredentials_Pool[SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE]; static struct SolidSyslogPoolAllocator MbedTlsHandleCredentials_Allocator = { MbedTlsHandleCredentials_InUse, SOLIDSYSLOG_TLS_CREDENTIALS_POOL_SIZE @@ -58,8 +56,7 @@ struct SolidSyslogMbedTlsCredentials* SolidSyslogMbedTlsHandleCredentials_Create /* 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 connection that first * presents a client credential. */ -static inline bool MbedTlsHandleCredentials_IsValidConfig( - const struct SolidSyslogMbedTlsHandleCredentialsConfig* config +static inline bool MbedTlsHandleCredentials_IsValidConfig(const struct SolidSyslogMbedTlsHandleCredentialsConfig* config ) { bool valid = false; diff --git a/docs/generated/MbedTls-manifest.txt b/docs/generated/MbedTls-manifest.txt index 81094822..8833f123 100644 --- a/docs/generated/MbedTls-manifest.txt +++ b/docs/generated/MbedTls-manifest.txt @@ -14,6 +14,8 @@ # MbedTls: Platform/MbedTls/Source/SolidSyslogMbedTlsNullCredentials.c +Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c +Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c Platform/MbedTls/Source/SolidSyslogMbedTlsStreamStatic.c Platform/MbedTls/Source/SolidSyslogMbedTlsHmacSha256Policy.c diff --git a/docs/generated/beta-stack-manifest.txt b/docs/generated/beta-stack-manifest.txt index 2fb32afa..dc078d2a 100644 --- a/docs/generated/beta-stack-manifest.txt +++ b/docs/generated/beta-stack-manifest.txt @@ -76,6 +76,8 @@ Core/Source/SolidSyslogNullAtomicCounter.c # MbedTls: Platform/MbedTls/Source/SolidSyslogMbedTlsNullCredentials.c +Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentials.c +Platform/MbedTls/Source/SolidSyslogMbedTlsHandleCredentialsStatic.c Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c Platform/MbedTls/Source/SolidSyslogMbedTlsStreamStatic.c Platform/MbedTls/Source/SolidSyslogMbedTlsHmacSha256Policy.c diff --git a/docs/hardening-path.md b/docs/hardening-path.md index 77a6d4d0..a6646315 100644 --- a/docs/hardening-path.md +++ b/docs/hardening-path.md @@ -446,12 +446,17 @@ The collector is authenticated to the device; the device is not yet authenticate collector. ```c +struct SolidSyslogMbedTlsHandleCredentialsConfig credentialsConfig = { + .Rng = DeviceCertStore_Rng(), + .CaChain = DeviceCertStore_CaChain(), +}; + struct SolidSyslogMbedTlsStreamConfig tlsConfig = { - .Transport = SolidSyslogLwipRawTcpStream_Create(&tcpConfig), - .Sleep = SyslogSleep, - .Rng = DeviceCertStore_Rng(), - .CaChain = DeviceCertStore_CaChain(), - .ServerName = SYSLOG_COLLECTOR_HOST, + .Transport = SolidSyslogLwipRawTcpStream_Create(&tcpConfig), + .Sleep = SyslogSleep, + .Rng = DeviceCertStore_Rng(), + .Credentials = SolidSyslogMbedTlsHandleCredentials_Create(&credentialsConfig), + .ServerName = SYSLOG_COLLECTOR_HOST, }; .Stream = SolidSyslogMbedTlsStream_Create(&tlsConfig), @@ -535,17 +540,18 @@ and its key makes the handshake mutual, so the receiver authenticates the device cryptographically. ```c -struct SolidSyslogMbedTlsStreamConfig tlsConfig = { +struct SolidSyslogMbedTlsHandleCredentialsConfig credentialsConfig = { /* ... as stage 13 ... */ .ClientCertChain = DeviceCertStore_ClientChain(), .ClientKey = DeviceCertStore_ClientKey(), }; ``` -Both must be set. Supplying one and not the other silently leaves the connection -server-authenticated rather than failing, which is exactly the weakening an auditor -would look for — which is why the pipeline element in stage 15 reports what is actually -in force rather than what was intended. +Both must be set. Supplying one and not the other is reported as a bad +configuration and leaves the connection server-authenticated rather than failing, +which is exactly the weakening an auditor would look for — which is why the +pipeline element in stage 15 reports what is actually in force rather than what +was intended. **What it authenticates is the TLS peer.** If the device connects to the collector directly, that is the device. If anything terminates the connection in between — a diff --git a/docs/platforms/mbedtls/index.md b/docs/platforms/mbedtls/index.md index 50b7d961..4e15c16b 100644 --- a/docs/platforms/mbedtls/index.md +++ b/docs/platforms/mbedtls/index.md @@ -8,8 +8,8 @@ integrity and confidentiality. What a TLS stream must do is the same whichever library provides it, and is stated once under [TLS obligations](../../tls.md). This page covers what this -adapter needs, the coexistence guarantee it makes, and where it does not yet meet -that contract. +adapter needs, how credentials reach it, the coexistence guarantee it makes, and +where it does not yet meet that contract. ## What it ships @@ -20,29 +20,34 @@ so the features you enable are the features it gets. A `SolidSyslogSleepFunction` is required and has no default. -## Credentials are handles, not paths +## Credentials come from a credentials source -Credentials are passed as caller-built, caller-owned handles: a seeded -`mbedtls_ctr_drbg_context` for the handshake, an `mbedtls_x509_crt` trust chain, +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. + +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 lifetimes are in play and they are not the same. The **handle objects** must -stay addressable for as long as the stream might open a connection, because the -adapter reads the pointers it was given on every connect. The **parsed material -inside them** only has to be intact while a connection is open, which is when the -adapter's `ssl_config` holds pointers into it. - -Rotation follows from the second lifetime. Call `SolidSyslogSender_Disconnect`, -which releases the `ssl_config` and with it every pointer into the material, 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. +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. -The adapter does not say when it has finished with the material, so an integrator -who wants the private key out of RAM between connections has to drive that -sequence themselves rather than being told. That is the gap recorded below. +Rotation with the shipped 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. ## Coexistence is an auditable contract @@ -56,8 +61,7 @@ claim can be checked against the directory. ## Where it differs from the contract -Four differences, each tracked. Read them before relying on the corresponding -obligation. +Each is tracked. Read them before relying on the corresponding obligation. ### A peer cannot be authorised by certificate fingerprint @@ -65,15 +69,6 @@ Only certification path validation is offered, so a deployment with no PKI has n way to pin the collector's certificate. Tracked as [#753](https://github.com/cososo-ltd/solid-syslog/issues/753). -### Credential material must stay parsed for the life of the stream - -The adapter binds the handles into its `ssl_config` on each connection and drops -them on close, but it never says so, so every handle has to remain valid and -parsed for as long as the stream exists. A device that connects rarely still -holds its private key in RAM continuously, and there is no point at which the -adapter invites the integrator to release it. Tracked under -[E39](https://github.com/cososo-ltd/solid-syslog/issues/782). - ### The cipher policy cannot be expressed The configuration carries no cipher or ciphersuite field, so the ciphersuites @@ -81,12 +76,3 @@ your `mbedtls_config.h` enables, filtered by the preset, are what gets negotiated. The contract asks for an integrator's policy to be passed through where the library allows one to be selected. Tracked as [#733](https://github.com/cososo-ltd/solid-syslog/issues/733). - -### A missing trust chain is not reported as a configuration fault - -`mbedtls_ssl_conf_ca_chain` returns no status, so a configuration carrying no -trust anchors is accepted, and the fault surfaces only once the peer's -certificate finds nothing to chain to. What is reported is an untrusted peer -rather than the missing trust material. Tracked -as [#753](https://github.com/cososo-ltd/solid-syslog/issues/753), which is where -a peer authorised by fingerprint instead of by trust anchor is settled. diff --git a/docs/platforms/mbedtls/setup.md b/docs/platforms/mbedtls/setup.md index d4677b93..4b422d9d 100644 --- a/docs/platforms/mbedtls/setup.md +++ b/docs/platforms/mbedtls/setup.md @@ -15,34 +15,56 @@ records; the transport underneath carries the bytes. SolidSyslog_Log ─▶ Buffer ─▶ SolidSyslogStreamSender │ ▼ - SolidSyslogMbedTlsStream ◀── your CA / cert / key / DRBG handles + SolidSyslogMbedTlsStream ◀── your DRBG handle + ▲ + └── a credentials source ◀── your CA / cert / key │ ▼ a byte-transport Stream ◀── your TCP/IP stack ``` -You supply two things: the byte transport, and the Mbed TLS handles. Everything +You supply three things: the byte transport, the credentials source the stream +asks for its material, and the DRBG the handshake runs on. Everything above the TLS stream is unchanged from a plaintext wiring — `StreamSender` 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: + ```c -struct SolidSyslogMbedTlsStreamConfig cfg = { - .Transport = myTcpStream, - .Sleep = MySleep, /* required — no fallback */ +struct SolidSyslogMbedTlsHandleCredentialsConfig credentialsConfig = { .Rng = &mySeededDrbg, .CaChain = &myParsedCaChain, - .ServerName = "syslog.example.com", .ClientCertChain = &myClientCert, /* both, or neither */ .ClientKey = &myClientKey, }; +struct SolidSyslogMbedTlsCredentials* credentials = + SolidSyslogMbedTlsHandleCredentials_Create(&credentialsConfig); +``` + +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: + +```c +struct SolidSyslogMbedTlsStreamConfig cfg = { + .Transport = myTcpStream, + .Sleep = MySleep, /* required — no fallback */ + .Rng = &mySeededDrbg, + .Credentials = credentials, /* required — no fallback */ + .ServerName = "syslog.example.com", +}; struct SolidSyslogStream* tls = SolidSyslogMbedTlsStream_Create(&cfg); ``` +Each Create copies its configuration, so every field has to be set before it is +called. + Wire `tls` into a `SolidSyslogStreamSender` as its `Stream`, exactly as you -would a plain TCP stream, and call `SolidSyslogMbedTlsStream_Destroy` when the -sender is torn down. There is nothing process-wide to install. +would a plain TCP stream. Tear down in reverse: the sender, the TLS stream, then +the credentials it borrows. There is nothing process-wide to install. If your firmware already uses Mbed TLS for something else — a cloud client, an OTA updater, a vendor framework — that is the whole integration: the adapter diff --git a/misra_suppressions.txt b/misra_suppressions.txt index 5c6bd0f5..4d5b6cee 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -54,7 +54,8 @@ misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpDatagram.c:57 misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c:52 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:114 +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/SolidSyslogMbedTlsHmacSha256Policy.c:83 misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicy.c:85 misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogOpenSslAesGcmPolicy.c:89 @@ -87,8 +88,8 @@ misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDnsResolver.c:158 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDnsResolver.c:220 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:154 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:162 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:439 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:451 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:423 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:435 # D.003 — Rule 5.7: repeating struct tags (no-typedef-struct convention) # See docs/misra-deviations.md#d003 @@ -143,7 +144,7 @@ misra-c2012-5.7:Core/Source/SolidSyslogStreamSender.c:29 misra-c2012-5.7:Core/Source/SolidSyslogUdpPayload.c:9 misra-c2012-5.7:Platform/PlusTcp/Source/SolidSyslogPlusTcpResolver.c:29 misra-c2012-5.7:Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c:40 -misra-c2012-5.7:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:29 +misra-c2012-5.7:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:30 misra-c2012-5.7:Platform/MbedTls/Source/SolidSyslogMbedTlsHmacSha256Policy.c:25 misra-c2012-5.7:Platform/MbedTls/Source/SolidSyslogMbedTlsAesGcmPolicy.c:28 misra-c2012-5.7:Platform/OpenSsl/Source/SolidSyslogOpenSslAesGcmPolicy.c:26 @@ -197,8 +198,8 @@ misra-c2012-8.9:Core/Source/SolidSyslogFileBlockDevice.c:24 # D.013 — Rule 11.5: void* <-> a byte pointer at third-party byte-buffer API boundaries # See docs/misra-deviations.md#d013 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:476 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:494 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:460 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:478 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:146 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:361 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:381