From 93319b665ace1268b6bc220d49207c7647549db5 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 31 May 2026 07:26:32 +0000 Subject: [PATCH 1/2] Revert "feat: S26.04 mbedTLS client-side TLS session resumption (#489)" This reverts commit d37ef315f54d7b6d1398ea4f3b72635b34eeea68. --- DEVLOG.md | 37 --- .../MbedTls/Source/SolidSyslogMbedTlsStream.c | 44 --- .../Source/SolidSyslogMbedTlsStreamPrivate.h | 3 - .../MbedTls/SolidSyslogMbedTlsStreamTest.cpp | 147 ---------- Tests/MbedTlsIntegration/CMakeLists.txt | 2 - .../MbedTlsResumptionServer.c | 261 ------------------ .../MbedTlsResumptionServer.h | 55 ---- .../ReconnectingSocketStream.c | 127 --------- .../ReconnectingSocketStream.h | 23 -- ...olidSyslogMbedTlsStreamIntegrationTest.cpp | 179 ------------ Tests/Support/MbedTlsFake.c | 128 --------- Tests/Support/MbedTlsFake.h | 24 -- docs/integrating-mbedtls.md | 35 --- misra_suppressions.txt | 10 +- 14 files changed, 5 insertions(+), 1070 deletions(-) delete mode 100644 Tests/MbedTlsIntegration/MbedTlsResumptionServer.c delete mode 100644 Tests/MbedTlsIntegration/MbedTlsResumptionServer.h delete mode 100644 Tests/MbedTlsIntegration/ReconnectingSocketStream.c delete mode 100644 Tests/MbedTlsIntegration/ReconnectingSocketStream.h diff --git a/DEVLOG.md b/DEVLOG.md index a0c0c3cb..a84a0e98 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -13336,40 +13336,3 @@ MISRA rule — different category, doesn't set precedent. - None outstanding. Forum post drafted for David to post under the Libraries category at his discretion; in-repo docs land via this PR. - -## 2026-05-30 — S26.04 mbedTLS client-side session resumption - -### Decisions -- **Mirror the OpenSSL story's intent, independent of its code.** S26.01 - (#280, OpenSSL) is still open; S26.04 is independent so we built the - mbedTLS backend first. Capture the negotiated session with - `mbedtls_ssl_get_session` after a successful handshake, feed it back via - `mbedtls_ssl_set_session` before the next handshake, on the same - `SolidSyslogMbedTlsStream` instance. -- **Free on Destroy, not Close.** The story said "Close / Destroy", but the - saved session must survive the S12.14 fail-fast Close to be resumable on - the next Open — so it lives for the life of the instance and is freed only - in `_Cleanup` (plus free-before-recapture so `get_session`'s deep copy - never leaks the prior peer cert / ticket). Eager `session_init` in Create - keeps that free always safe. -- **Best-effort, silent.** A failed capture or restore never fails the Open - and emits no error code — resumption is an optimisation, not a delivery - precondition; the sibling OpenSSL story emits nothing either. Keeps the - error channel meaningful. -- **Integration observable = server-side ticket-parse wrapper.** mbedTLS has - no client-side `SSL_session_reused()`, so the in-test server wraps its - ticket write/parse callbacks (shared `p_ticket`) and records when a - presented ticket parses successfully. A new reconnecting socketpair - transport double feeds the stream a fresh connection per Open (no TCP - ports → deterministic); a two-handshake ticket server shares one ticket - key for the resume case and rotates it for the non-resuming-peer fallback. - Verified non-vacuous by disabling the production restore and watching only - the resume assertion fail while delivery still passed. - -### Deferred -- **Test/code hygiene pass.** David will take a separate PR for tidy-ups on - the new tests and adapter. -- **S26.01 (OpenSSL backend).** Still open; can now mirror this one. - -### Open questions -- None outstanding. diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c index 63a90b6a..fcc308cb 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c @@ -33,8 +33,6 @@ static inline bool MbedTlsStream_BindContextToConfig(struct SolidSyslogMbedTlsSt static inline bool MbedTlsStream_ConfigureExpectedHostname(struct SolidSyslogMbedTlsStream* self); static inline void MbedTlsStream_InstallTransportCallbacks(struct SolidSyslogMbedTlsStream* self); static inline bool MbedTlsStream_PerformHandshake(struct SolidSyslogMbedTlsStream* self); -static inline void MbedTlsStream_RestoreSession(struct SolidSyslogMbedTlsStream* self); -static inline void MbedTlsStream_CaptureSession(struct SolidSyslogMbedTlsStream* self); static inline bool MbedTlsStream_IsRetryableHandshakeRc(int rc); static inline bool MbedTlsStream_IsHandshakeBudgetExhausted(uint32_t totalSleptMs, uint32_t budgetMs); static inline bool MbedTlsStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size); @@ -66,12 +64,6 @@ void MbedTlsStream_Initialise(struct SolidSyslogStream* base, const struct Solid * works without re-init. */ mbedtls_ssl_init(&self->SslContext); mbedtls_ssl_config_init(&self->SslConfig); - /* Eager init so the session_free in Cleanup is always safe, whether or - * not a session was ever captured. The saved session outlives Close (it - * must survive the fail-fast reconnect to be resumable) and is freed only - * on Destroy. */ - mbedtls_ssl_session_init(&self->SavedSession); - self->HasSavedSession = false; } /* Null Object substituted in Initialise when the integrator does not install a @@ -104,15 +96,9 @@ static inline struct SolidSyslogMbedTlsStream* MbedTlsStream_SelfFromBase(struct void MbedTlsStream_Cleanup(struct SolidSyslogStream* base) { - struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base); /* Mirror the OpenSSL TlsStream pattern: an integrator who destroys a * still-Open stream must not leak the underlying TLS state. */ MbedTlsStream_Close(base); - /* Release the saved resumption session — Destroy is the one site that - * frees it, since it must survive every Close to stay resumable. Safe on - * an empty session thanks to the eager init in Initialise. */ - mbedtls_ssl_session_free(&self->SavedSession); - self->HasSavedSession = false; /* Overwrite the abstract base with the shared NullStream vtable so * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ *base = *SolidSyslogNullStream_Get(); @@ -143,13 +129,8 @@ static inline bool MbedTlsStream_Open(struct SolidSyslogStream* base, const stru if (ok) { MbedTlsStream_InstallTransportCallbacks(self); - MbedTlsStream_RestoreSession(self); ok = MbedTlsStream_PerformHandshake(self); } - if (ok) - { - MbedTlsStream_CaptureSession(self); - } if (!ok) { MbedTlsStream_Close(base); @@ -157,31 +138,6 @@ static inline bool MbedTlsStream_Open(struct SolidSyslogStream* base, const stru return ok; } -/* Feed a previously-captured session back so this handshake can resume it. - * No-op on the first connect (nothing saved yet). */ -static inline void MbedTlsStream_RestoreSession(struct SolidSyslogMbedTlsStream* self) -{ - if (self->HasSavedSession) - { - (void) mbedtls_ssl_set_session(&self->SslContext, &self->SavedSession); - } -} - -/* Save the just-negotiated session so the next Open of this instance can - * resume it. Best-effort: a non-zero return leaves HasSavedSession false so - * the next connect simply does a full handshake. */ -static inline void MbedTlsStream_CaptureSession(struct SolidSyslogMbedTlsStream* self) -{ - /* get_session deep-copies into SavedSession; free any prior session first - * so its peer cert / ticket allocations are not leaked. */ - if (self->HasSavedSession) - { - mbedtls_ssl_session_free(&self->SavedSession); - self->HasSavedSession = false; - } - self->HasSavedSession = mbedtls_ssl_get_session(&self->SslContext, &self->SavedSession) == 0; -} - static inline bool MbedTlsStream_ApplySslConfigDefaults(struct SolidSyslogMbedTlsStream* self) { bool ok = mbedtls_ssl_config_defaults( diff --git a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamPrivate.h b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamPrivate.h index 4a76ef09..e6661a89 100644 --- a/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamPrivate.h +++ b/Platform/MbedTls/Source/SolidSyslogMbedTlsStreamPrivate.h @@ -2,7 +2,6 @@ #define SOLIDSYSLOGMBEDTLSSTREAMPRIVATE_H #include -#include #include "SolidSyslogMbedTlsStream.h" #include "SolidSyslogStreamDefinition.h" @@ -13,8 +12,6 @@ struct SolidSyslogMbedTlsStream struct SolidSyslogMbedTlsStreamConfig Config; mbedtls_ssl_config SslConfig; mbedtls_ssl_context SslContext; - mbedtls_ssl_session SavedSession; /* resumption: captured after handshake, fed back on next Open */ - bool HasSavedSession; }; void MbedTlsStream_Initialise(struct SolidSyslogStream* base, const struct SolidSyslogMbedTlsStreamConfig* config); diff --git a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp index 13aaafbf..8ce71acb 100644 --- a/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp +++ b/Tests/MbedTls/SolidSyslogMbedTlsStreamTest.cpp @@ -159,16 +159,6 @@ TEST(SolidSyslogMbedTlsStream, CreateInitialisesSslConfigForSafeFree) LONGS_EQUAL(1, MbedTlsFake_SslConfigInitCallCount()); } -TEST(SolidSyslogMbedTlsStream, CreateInitialisesSavedSessionForSafeFree) - -{ - /* The per-instance saved-session slot (session resumption, S26.04) is - * init'd eagerly in Create so the session_free in Destroy is always safe - * — whether a session was ever captured or not. Same eager-init invariant - * as the SslConfig / SslContext cases. */ - LONGS_EQUAL(1, MbedTlsFake_SslSessionInitCallCount()); -} - TEST(SolidSyslogMbedTlsStream, OpenAppliesClientStreamDefaultsToSslConfig) { @@ -240,143 +230,6 @@ TEST(SolidSyslogMbedTlsStream, OpenReturnsFalseWhenHandshakeFails) CHECK_FALSE(SolidSyslogStream_Open(handle, addr)); } -/* ------------------------------------------------------------------------- - * Session resumption (S26.04). After a successful handshake the adapter - * captures the negotiated session into its per-instance SavedSession slot, - * feeds it back via mbedtls_ssl_set_session before the next handshake, and - * frees it on Destroy (not Close — it must survive the fail-fast reconnect). - * Best-effort: any failure falls back to a full handshake; delivery never - * depends on resumption. - * ------------------------------------------------------------------------- */ - -TEST(SolidSyslogMbedTlsStream, OpenCapturesSessionWhenHandshakeSucceeds) - -{ - MbedTlsFake_SetSslHandshakeReturn(0); - - SolidSyslogStream_Open(handle, addr); - - LONGS_EQUAL(1, MbedTlsFake_SslGetSessionCallCount()); - POINTERS_EQUAL(MbedTlsFake_LastSslInitArg(), MbedTlsFake_LastSslGetSessionContextArg()); - POINTERS_EQUAL(MbedTlsFake_LastSslSessionInitArg(), MbedTlsFake_LastSslGetSessionSessionArg()); -} - -TEST(SolidSyslogMbedTlsStream, OpenDoesNotCaptureSessionWhenHandshakeFails) - -{ - /* A failed handshake leaves no session to save — capture must be gated on - * handshake success so we never resume from a half-built session. */ - MbedTlsFake_SetSslHandshakeReturn(-1); - - SolidSyslogStream_Open(handle, addr); - - LONGS_EQUAL(0, MbedTlsFake_SslGetSessionCallCount()); -} - -TEST(SolidSyslogMbedTlsStream, OpenSucceedsEvenWhenSessionCaptureFails) - -{ - /* Resumption is best-effort: a get_session failure must not fail the Open - * — the handshake completed, the message can be delivered, the next - * connect simply won't resume. */ - MbedTlsFake_SetSslHandshakeReturn(0); - MbedTlsFake_SetSslGetSessionReturn(-1); - - CHECK_TRUE(SolidSyslogStream_Open(handle, addr)); -} - -TEST(SolidSyslogMbedTlsStream, FirstOpenDoesNotRestoreSession) - -{ - /* Nothing captured yet on a fresh stream, so the first handshake must run - * full — no set_session call. */ - MbedTlsFake_SetSslHandshakeReturn(0); - - SolidSyslogStream_Open(handle, addr); - - LONGS_EQUAL(0, MbedTlsFake_SslSetSessionCallCount()); -} - -TEST(SolidSyslogMbedTlsStream, SecondOpenRestoresSavedSessionBeforeHandshake) - -{ - /* First Open captures; the reconnect Close tears the connection down; the - * second Open of the same instance feeds the saved session back via - * set_session *before* the handshake it primes. */ - MbedTlsFake_SetSslHandshakeReturn(0); - SolidSyslogStream_Open(handle, addr); - SolidSyslogStream_Close(handle); - - SolidSyslogStream_Open(handle, addr); - - LONGS_EQUAL(1, MbedTlsFake_SslSetSessionCallCount()); - POINTERS_EQUAL(MbedTlsFake_LastSslInitArg(), MbedTlsFake_LastSslSetSessionContextArg()); - POINTERS_EQUAL(MbedTlsFake_LastSslSessionInitArg(), MbedTlsFake_LastSslSetSessionSessionArg()); - /* One handshake (the first Open's) had completed when set_session ran, - * proving the restore precedes the second Open's handshake. */ - LONGS_EQUAL(1, MbedTlsFake_SslSetSessionHandshakeCountAtCall()); -} - -TEST(SolidSyslogMbedTlsStream, SecondOpenSucceedsWhenSessionRestoreFails) - -{ - /* Best-effort restore: a set_session failure must not fail the Open — the - * adapter falls through to a full handshake and still delivers. */ - MbedTlsFake_SetSslHandshakeReturn(0); - SolidSyslogStream_Open(handle, addr); - SolidSyslogStream_Close(handle); - MbedTlsFake_SetSslSetSessionReturn(-1); - - CHECK_TRUE(SolidSyslogStream_Open(handle, addr)); -} - -TEST(SolidSyslogMbedTlsStream, CloseDoesNotFreeSavedSession) - -{ - /* The lifecycle lock-in: Close runs on every fail-fast reconnect, so it - * must NOT free the saved session — otherwise the very next Open could - * never resume. The session is freed only on Destroy. */ - MbedTlsFake_SetSslHandshakeReturn(0); - SolidSyslogStream_Open(handle, addr); - - SolidSyslogStream_Close(handle); - - LONGS_EQUAL(0, MbedTlsFake_SslSessionFreeCallCount()); -} - -TEST(SolidSyslogMbedTlsStream, DestroyFreesSavedSession) - -{ - /* Destroy is the one site that releases the saved session — symmetric with - * the eager session_init in Create. */ - MbedTlsFake_SetSslHandshakeReturn(0); - SolidSyslogStream_Open(handle, addr); - - SolidSyslogMbedTlsStream_Destroy(handle); - LONGS_EQUAL(1, MbedTlsFake_SslSessionFreeCallCount()); - POINTERS_EQUAL(MbedTlsFake_LastSslSessionInitArg(), MbedTlsFake_LastSslSessionFreeArg()); - - handle = SolidSyslogMbedTlsStream_Create(&config); /* keep teardown's Destroy valid */ -} - -TEST(SolidSyslogMbedTlsStream, SecondCaptureFreesPreviousSession) - -{ - /* mbedtls_ssl_get_session deep-copies into the destination; capturing a - * second session over a populated slot would leak the first's peer cert / - * ticket. The recapture must free the prior session first. Close in - * between does not free (lifecycle), so exactly one free is observed. */ - MbedTlsFake_SetSslHandshakeReturn(0); - SolidSyslogStream_Open(handle, addr); /* capture #1 */ - SolidSyslogStream_Close(handle); /* survives — no free */ - - SolidSyslogStream_Open(handle, addr); /* recapture #2 frees #1 first */ - - LONGS_EQUAL(1, MbedTlsFake_SslSessionFreeCallCount()); - LONGS_EQUAL(2, MbedTlsFake_SslGetSessionCallCount()); - POINTERS_EQUAL(MbedTlsFake_LastSslSessionInitArg(), MbedTlsFake_LastSslSessionFreeArg()); -} - /* ------------------------------------------------------------------------- * Bounded handshake retry loop. mbedtls_ssl_handshake under non-blocking * transport will emit MBEDTLS_ERR_SSL_WANT_READ / WANT_WRITE between RTTs; diff --git a/Tests/MbedTlsIntegration/CMakeLists.txt b/Tests/MbedTlsIntegration/CMakeLists.txt index 141fa385..a8c554da 100644 --- a/Tests/MbedTlsIntegration/CMakeLists.txt +++ b/Tests/MbedTlsIntegration/CMakeLists.txt @@ -48,10 +48,8 @@ add_executable(MbedTlsIntegrationTests main.cpp SolidSyslogMbedTlsStreamIntegrationTest.cpp SocketStream.c - ReconnectingSocketStream.c MbedTlsTestCert.c MbedTlsTestServer.c - MbedTlsResumptionServer.c ${CMAKE_SOURCE_DIR}/Tests/AddressFake.c ${CMAKE_SOURCE_DIR}/Tests/Support/SafeStringStandard.c ${CMAKE_SOURCE_DIR}/Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c diff --git a/Tests/MbedTlsIntegration/MbedTlsResumptionServer.c b/Tests/MbedTlsIntegration/MbedTlsResumptionServer.c deleted file mode 100644 index 4caff602..00000000 --- a/Tests/MbedTlsIntegration/MbedTlsResumptionServer.c +++ /dev/null @@ -1,261 +0,0 @@ -#include "MbedTlsResumptionServer.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "MbedTlsTestCert.h" -#include "mbedtls/pk.h" - -enum -{ - CONNECTION_COUNT = 2, - TICKET_LIFETIME_SECONDS = 86400, - APP_RECORD_BUFFER_BYTES = 512 -}; - -/* Shared by the wrapped ticket write / parse callbacks. `Ctx` is the real - * ticket context the wrappers forward to; swapping it before the second - * connection is how the rotate-key (non-resuming-peer) case is staged. - * `Accepted` is set true when a presented ticket parses successfully — the - * stand-in for mbedTLS's missing client-side SSL_session_reused(). */ -struct TicketCallbackContext -{ - mbedtls_ssl_ticket_context* Ctx; - bool Accepted; -}; - -struct MbedTlsResumptionServer -{ - int Fds[CONNECTION_COUNT]; - bool RotateKey; - mbedtls_ctr_drbg_context* Rng; - const struct MbedTlsTestCert* ServerCert; - mbedtls_ssl_config Conf; - mbedtls_ssl_ticket_context TicketCtxPrimary; - mbedtls_ssl_ticket_context TicketCtxRotated; - struct TicketCallbackContext TicketCb; - pthread_t Thread; - bool Joined; - bool HandshakeSucceeded[CONNECTION_COUNT]; - bool MessageDelivered[CONNECTION_COUNT]; - bool ResumeAccepted[CONNECTION_COUNT]; -}; - -static void* RunServer(void* arg); -static int WrappedTicketWrite( - void* p_ticket, - const mbedtls_ssl_session* session, - unsigned char* start, - const unsigned char* end, - size_t* tlen, - uint32_t* lifetime -); -static int WrappedTicketParse(void* p_ticket, mbedtls_ssl_session* session, unsigned char* buf, size_t len); -static int ServerBioSend(void* ctx, const unsigned char* buf, size_t len); -static int ServerBioRecv(void* ctx, unsigned char* buf, size_t len); - -struct MbedTlsResumptionServer* MbedTlsResumptionServer_Create(const struct MbedTlsResumptionServerConfig* config) -{ - struct MbedTlsResumptionServer* self = - (struct MbedTlsResumptionServer*) calloc(1U, sizeof(struct MbedTlsResumptionServer)); - self->Fds[0] = config->ServerFds[0]; - self->Fds[1] = config->ServerFds[1]; - self->RotateKey = config->RotateTicketKeyBetweenConnections; - self->Rng = config->Rng; - self->ServerCert = config->ServerCert; - - mbedtls_ssl_config_init(&self->Conf); - mbedtls_ssl_config_defaults( - &self->Conf, - MBEDTLS_SSL_IS_SERVER, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT - ); - /* Pin TLS 1.2 — same rationale as MbedTlsTestServer: keeps the rejection / - * teardown cadence synchronous over a socketpair, and the TLS 1.2 ticket - * arrives in-handshake so the client can capture it immediately. */ - mbedtls_ssl_conf_max_tls_version(&self->Conf, MBEDTLS_SSL_VERSION_TLS1_2); - mbedtls_ssl_conf_rng(&self->Conf, mbedtls_ctr_drbg_random, self->Rng); - /* Server-auth only — these tests exercise resumption, not client identity. */ - mbedtls_ssl_conf_authmode(&self->Conf, MBEDTLS_SSL_VERIFY_NONE); - mbedtls_ssl_conf_own_cert( - &self->Conf, - (mbedtls_x509_crt*) &self->ServerCert->Cert, - (mbedtls_pk_context*) &self->ServerCert->Key - ); - - mbedtls_ssl_ticket_init(&self->TicketCtxPrimary); - mbedtls_ssl_ticket_setup( - &self->TicketCtxPrimary, - mbedtls_ctr_drbg_random, - self->Rng, - MBEDTLS_CIPHER_AES_256_GCM, - TICKET_LIFETIME_SECONDS - ); - mbedtls_ssl_ticket_init(&self->TicketCtxRotated); /* setup lazily in RunServer if rotating */ - self->TicketCb.Ctx = &self->TicketCtxPrimary; - self->TicketCb.Accepted = false; - mbedtls_ssl_conf_session_tickets_cb(&self->Conf, WrappedTicketWrite, WrappedTicketParse, &self->TicketCb); - - pthread_create(&self->Thread, NULL, RunServer, self); - return self; -} - -void MbedTlsResumptionServer_Destroy(struct MbedTlsResumptionServer* self) -{ - if (self != NULL) - { - if (!self->Joined) - { - /* Unblock a worker that may be parked in recv on a wedged - * negative-path test, then join. */ - for (int i = 0; i < CONNECTION_COUNT; i++) - { - if (self->Fds[i] >= 0) - { - shutdown(self->Fds[i], SHUT_RDWR); - } - } - pthread_join(self->Thread, NULL); - self->Joined = true; - } - mbedtls_ssl_config_free(&self->Conf); - mbedtls_ssl_ticket_free(&self->TicketCtxPrimary); - mbedtls_ssl_ticket_free(&self->TicketCtxRotated); - for (int i = 0; i < CONNECTION_COUNT; i++) - { - if (self->Fds[i] >= 0) - { - close(self->Fds[i]); - } - } - free(self); - } -} - -void MbedTlsResumptionServer_Join(struct MbedTlsResumptionServer* self) -{ - if (!self->Joined) - { - pthread_join(self->Thread, NULL); - self->Joined = true; - } -} - -bool MbedTlsResumptionServer_BothHandshakesSucceeded(struct MbedTlsResumptionServer* self) -{ - return self->HandshakeSucceeded[0] && self->HandshakeSucceeded[1]; -} - -bool MbedTlsResumptionServer_BothMessagesDelivered(struct MbedTlsResumptionServer* self) -{ - return self->MessageDelivered[0] && self->MessageDelivered[1]; -} - -bool MbedTlsResumptionServer_SecondHandshakeResumed(struct MbedTlsResumptionServer* self) -{ - return self->ResumeAccepted[1]; -} - -/* Drives two consecutive server-side handshakes — one per fd — reading a - * single application record after each so the test can assert delivery on - * both the full and the resumed connection. */ -static void* RunServer(void* arg) -{ - struct MbedTlsResumptionServer* self = (struct MbedTlsResumptionServer*) arg; - - for (int i = 0; i < CONNECTION_COUNT; i++) - { - if ((i == 1) && self->RotateKey) - { - /* Fresh key the client's saved ticket cannot decrypt -> the second - * handshake falls back to full. */ - mbedtls_ssl_ticket_setup( - &self->TicketCtxRotated, - mbedtls_ctr_drbg_random, - self->Rng, - MBEDTLS_CIPHER_AES_256_GCM, - TICKET_LIFETIME_SECONDS - ); - self->TicketCb.Ctx = &self->TicketCtxRotated; - } - self->TicketCb.Accepted = false; - - mbedtls_ssl_context ssl; - mbedtls_ssl_init(&ssl); - mbedtls_ssl_setup(&ssl, &self->Conf); - mbedtls_ssl_set_bio(&ssl, &self->Fds[i], ServerBioSend, ServerBioRecv, NULL); - - int handshakeRc = 0; - do - { - handshakeRc = mbedtls_ssl_handshake(&ssl); - } while ((handshakeRc == MBEDTLS_ERR_SSL_WANT_READ) || (handshakeRc == MBEDTLS_ERR_SSL_WANT_WRITE)); - - self->HandshakeSucceeded[i] = (handshakeRc == 0); - self->ResumeAccepted[i] = self->TicketCb.Accepted; - - if (handshakeRc == 0) - { - unsigned char buffer[APP_RECORD_BUFFER_BYTES]; - int readRc = 0; - do - { - readRc = mbedtls_ssl_read(&ssl, buffer, sizeof(buffer)); - } while ((readRc == MBEDTLS_ERR_SSL_WANT_READ) || (readRc == MBEDTLS_ERR_SSL_WANT_WRITE)); - self->MessageDelivered[i] = (readRc > 0); - } - - mbedtls_ssl_free(&ssl); - } - return NULL; -} - -static int WrappedTicketWrite( - void* p_ticket, - const mbedtls_ssl_session* session, - unsigned char* start, - const unsigned char* end, - size_t* tlen, - uint32_t* lifetime -) -{ - struct TicketCallbackContext* cb = (struct TicketCallbackContext*) p_ticket; - return mbedtls_ssl_ticket_write(cb->Ctx, session, start, end, tlen, lifetime); -} - -static int WrappedTicketParse(void* p_ticket, mbedtls_ssl_session* session, unsigned char* buf, size_t len) -{ - struct TicketCallbackContext* cb = (struct TicketCallbackContext*) p_ticket; - int rc = mbedtls_ssl_ticket_parse(cb->Ctx, session, buf, len); - if (rc == 0) - { - cb->Accepted = true; /* a valid ticket was presented -> this handshake resumed */ - } - return rc; -} - -static int ServerBioSend(void* ctx, const unsigned char* buf, size_t len) -{ - int fd = *(int*) ctx; - ssize_t n = send(fd, buf, len, 0); - return (n >= 0) ? (int) n : -1; -} - -static int ServerBioRecv(void* ctx, unsigned char* buf, size_t len) -{ - int fd = *(int*) ctx; - ssize_t n = recv(fd, buf, len, 0); - return (n >= 0) ? (int) n : -1; -} diff --git a/Tests/MbedTlsIntegration/MbedTlsResumptionServer.h b/Tests/MbedTlsIntegration/MbedTlsResumptionServer.h deleted file mode 100644 index b8276c44..00000000 --- a/Tests/MbedTlsIntegration/MbedTlsResumptionServer.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef MBEDTLSRESUMPTIONSERVER_H -#define MBEDTLSRESUMPTIONSERVER_H - -#include -#include - -#include "ExternC.h" - -struct MbedTlsTestCert; - -EXTERN_C_BEGIN - - struct MbedTlsResumptionServer; - - struct MbedTlsResumptionServerConfig - { - int ServerFds[2]; /* the two socketpair server ends; ownership transfers to the server */ - const struct MbedTlsTestCert* ServerCert; /* server cert + matching key */ - mbedtls_ctr_drbg_context* Rng; /* shared with the test fixture */ - /* false: one ticket key shared across both connections -> the second - * handshake resumes. - * true: the ticket key is rotated before the second connection so the - * client's saved ticket no longer decrypts -> the server falls - * back to a full handshake (the non-resuming-peer case). */ - bool RotateTicketKeyBetweenConnections; - }; - - /* Spawns a worker that issues session tickets and drives two consecutive - * server-side handshakes (one per ServerFds entry), then reads one - * application record on each. The server owns both fds from Create. */ - struct MbedTlsResumptionServer* MbedTlsResumptionServer_Create(const struct MbedTlsResumptionServerConfig* config); - - /* Joins the worker and tears down all mbedTLS state. */ - void MbedTlsResumptionServer_Destroy(struct MbedTlsResumptionServer * self); - - /* Joins the worker thread. Callable once; the getters below are valid - * afterwards. */ - void MbedTlsResumptionServer_Join(struct MbedTlsResumptionServer * self); - - /* True when both handshakes completed successfully. */ - bool MbedTlsResumptionServer_BothHandshakesSucceeded(struct MbedTlsResumptionServer * self); - - /* True when an application record was received on both connections — - * proves delivery survives whichever handshake path was taken. */ - bool MbedTlsResumptionServer_BothMessagesDelivered(struct MbedTlsResumptionServer * self); - - /* The observable: true when the SECOND handshake presented a ticket the - * server accepted (an abbreviated / resumed handshake). mbedTLS exposes no - * client-side SSL_session_reused() equivalent, so the server records this - * by wrapping its ticket-parse callback. */ - bool MbedTlsResumptionServer_SecondHandshakeResumed(struct MbedTlsResumptionServer * self); - -EXTERN_C_END - -#endif /* MBEDTLSRESUMPTIONSERVER_H */ diff --git a/Tests/MbedTlsIntegration/ReconnectingSocketStream.c b/Tests/MbedTlsIntegration/ReconnectingSocketStream.c deleted file mode 100644 index 821ac7cb..00000000 --- a/Tests/MbedTlsIntegration/ReconnectingSocketStream.c +++ /dev/null @@ -1,127 +0,0 @@ -#include "ReconnectingSocketStream.h" - -#include -#include -#include -#include -#include - -#include "SolidSyslogStream.h" -#include "SolidSyslogStreamDefinition.h" - -struct SolidSyslogAddress; - -enum -{ - RECONNECTING_SOCKET_STREAM_MAX_FDS = 4 -}; - -struct ReconnectingSocketStream -{ - struct SolidSyslogStream Base; - int Fds[RECONNECTING_SOCKET_STREAM_MAX_FDS]; - int Count; - int NextIndex; /* index of the fd the next Open will consume */ - int CurrentFd; /* fd handed out by the most recent Open; -1 when closed */ -}; - -static bool ReconnectingSocketStream_Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); -static bool ReconnectingSocketStream_Send(struct SolidSyslogStream* self, const void* buffer, size_t size); -static SolidSyslogSsize ReconnectingSocketStream_Read(struct SolidSyslogStream* self, void* buffer, size_t size); -static void ReconnectingSocketStream_Close(struct SolidSyslogStream* self); - -struct SolidSyslogStream* ReconnectingSocketStream_Create(const int* fds, int count) -{ - struct ReconnectingSocketStream* stream = - (struct ReconnectingSocketStream*) calloc(1, sizeof(struct ReconnectingSocketStream)); - stream->Base.Open = ReconnectingSocketStream_Open; - stream->Base.Send = ReconnectingSocketStream_Send; - stream->Base.Read = ReconnectingSocketStream_Read; - stream->Base.Close = ReconnectingSocketStream_Close; - stream->Count = (count < RECONNECTING_SOCKET_STREAM_MAX_FDS) ? count : RECONNECTING_SOCKET_STREAM_MAX_FDS; - for (int i = 0; i < stream->Count; i++) - { - stream->Fds[i] = fds[i]; - } - stream->NextIndex = 0; - stream->CurrentFd = -1; - return &stream->Base; -} - -void ReconnectingSocketStream_Destroy(struct SolidSyslogStream* self) -{ - struct ReconnectingSocketStream* stream = (struct ReconnectingSocketStream*) self; - ReconnectingSocketStream_Close(self); - /* Close any fds the test never consumed (e.g. a test that failed before - * the second Open) so the harness leaks no descriptors. */ - for (int i = stream->NextIndex; i < stream->Count; i++) - { - if (stream->Fds[i] >= 0) - { - close(stream->Fds[i]); - stream->Fds[i] = -1; - } - } - free(stream); -} - -static bool ReconnectingSocketStream_Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) -{ - (void) addr; - struct ReconnectingSocketStream* stream = (struct ReconnectingSocketStream*) self; - bool ok = false; - if (stream->NextIndex < stream->Count) - { - stream->CurrentFd = stream->Fds[stream->NextIndex]; - stream->NextIndex++; - ok = true; - } - return ok; -} - -static bool ReconnectingSocketStream_Send(struct SolidSyslogStream* self, const void* buffer, size_t size) -{ - struct ReconnectingSocketStream* stream = (struct ReconnectingSocketStream*) self; - const unsigned char* bytes = (const unsigned char*) buffer; - size_t remaining = size; - bool ok = true; - while ((remaining > 0) && ok) - { - ssize_t n = send(stream->CurrentFd, bytes, remaining, 0); - if (n <= 0) - { - ok = false; - } - else - { - bytes += n; - remaining -= (size_t) n; - } - } - return ok; -} - -static SolidSyslogSsize ReconnectingSocketStream_Read(struct SolidSyslogStream* self, void* buffer, size_t size) -{ - /* Same EOF / would-block mapping as SocketStream: recv == 0 means the peer - * closed (fatal -> -1); the Stream contract reserves 0 for would-block, - * which a blocking socketpair never produces. */ - struct ReconnectingSocketStream* stream = (struct ReconnectingSocketStream*) self; - ssize_t n = recv(stream->CurrentFd, buffer, size, 0); - SolidSyslogSsize result = (SolidSyslogSsize) n; - if (n == 0) - { - result = -1; - } - return result; -} - -static void ReconnectingSocketStream_Close(struct SolidSyslogStream* self) -{ - struct ReconnectingSocketStream* stream = (struct ReconnectingSocketStream*) self; - if (stream->CurrentFd >= 0) - { - close(stream->CurrentFd); - stream->CurrentFd = -1; - } -} diff --git a/Tests/MbedTlsIntegration/ReconnectingSocketStream.h b/Tests/MbedTlsIntegration/ReconnectingSocketStream.h deleted file mode 100644 index 3b1ff1e4..00000000 --- a/Tests/MbedTlsIntegration/ReconnectingSocketStream.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef RECONNECTINGSOCKETSTREAM_H -#define RECONNECTINGSOCKETSTREAM_H - -#include "ExternC.h" - -struct SolidSyslogStream; - -EXTERN_C_BEGIN - - /* A SolidSyslogStream wrapper that models a *reconnectable* transport for - * the session-resumption integration tests. Unlike SocketStream (one fixed - * fd, no-op Open), each Open consumes the next fd from a caller-supplied - * queue, so the library's real Close -> Open fail-fast reconnect lands on a - * fresh connection — exactly what session resumption needs to be observed - * end-to-end. The fds are pre-connected socketpair ends (no TCP ports), so - * the harness stays deterministic. Ownership of the fds transfers to the - * stream: Close closes the current one, Destroy closes any still open. */ - struct SolidSyslogStream* ReconnectingSocketStream_Create(const int* fds, int count); - void ReconnectingSocketStream_Destroy(struct SolidSyslogStream * self); - -EXTERN_C_END - -#endif /* RECONNECTINGSOCKETSTREAM_H */ diff --git a/Tests/MbedTlsIntegration/SolidSyslogMbedTlsStreamIntegrationTest.cpp b/Tests/MbedTlsIntegration/SolidSyslogMbedTlsStreamIntegrationTest.cpp index 122b2ec5..4eb64f57 100644 --- a/Tests/MbedTlsIntegration/SolidSyslogMbedTlsStreamIntegrationTest.cpp +++ b/Tests/MbedTlsIntegration/SolidSyslogMbedTlsStreamIntegrationTest.cpp @@ -8,10 +8,8 @@ extern "C" #include #include -#include "MbedTlsResumptionServer.h" #include "MbedTlsTestCert.h" #include "MbedTlsTestServer.h" -#include "ReconnectingSocketStream.h" #include "SocketStream.h" #include "SolidSyslogMbedTlsStream.h" #include "AddressFake.h" @@ -306,180 +304,3 @@ TEST(SolidSyslogMbedTlsStreamIntegration, BinaryLinksAgainstRealLibMbedTls) const unsigned int major = (mbedtls_version_get_number() >> 24) & 0xFFU; LONGS_EQUAL(3, major); } - -/* ------------------------------------------------------------------------- - * Session resumption (S26.04). Drives two consecutive handshakes on the SAME - * SolidSyslogMbedTlsStream instance — the library's real Close -> Open - * fail-fast reconnect — against a ticket-issuing server. The observable is - * server-side: mbedTLS has no client-side SSL_session_reused(), so the server - * wraps its ticket-parse callback to report when a presented ticket was - * accepted. A reconnecting transport feeds the stream a fresh socketpair on - * each Open; the TLS layer is still wired straight to the oracle (no - * production TCP stream in the loop). - * ------------------------------------------------------------------------- */ - -namespace -{ -void SetRecvTimeout(int fd, int seconds) -{ - struct timeval rcvTimeout = {seconds, 0}; - setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &rcvTimeout, sizeof(rcvTimeout)); -} -} // namespace - -// clang-format off -TEST_GROUP(SolidSyslogMbedTlsStreamResumption) -{ - mbedtls_entropy_context entropy = {}; - mbedtls_ctr_drbg_context rng = {}; - int clientFds[2] = {-1, -1}; - int serverFds[2] = {-1, -1}; - struct MbedTlsTestCert trustedCa = {}; - struct MbedTlsTestCert serverCert = {}; - struct MbedTlsResumptionServer* server = nullptr; - struct SolidSyslogStream* transport = nullptr; - struct SolidSyslogStream* tlsStream = nullptr; - struct SolidSyslogAddress* addr = nullptr; - - void setup() override - { - addr = AddressFake_Get(); - mbedtls_entropy_init(&entropy); - mbedtls_ctr_drbg_init(&rng); - const unsigned char pers[] = "mbedtls-resumption-test"; - mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, pers, sizeof(pers) - 1U); - - /* Two pre-connected socketpairs: one per handshake. The reconnecting - * transport consumes the client ends in order; the server thread - * services the server ends in order. No TCP ports -> deterministic. */ - int pairA[2] = {-1, -1}; - int pairB[2] = {-1, -1}; - socketpair(AF_UNIX, SOCK_STREAM, 0, pairA); - socketpair(AF_UNIX, SOCK_STREAM, 0, pairB); - clientFds[0] = pairA[0]; - clientFds[1] = pairB[0]; - serverFds[0] = pairA[1]; - serverFds[1] = pairB[1]; - for (int fd : {clientFds[0], clientFds[1], serverFds[0], serverFds[1]}) - { - SetRecvTimeout(fd, 5); - } - - struct MbedTlsTestCertConfig caConfig = {}; - caConfig.SubjectName = TEST_CA_SUBJECT; - caConfig.IsCa = 1; - MbedTlsTestCert_Create(&caConfig, &trustedCa, &rng); - - struct MbedTlsTestCertConfig serverConfig = {}; - serverConfig.SubjectName = TEST_SERVER_SUBJECT; - serverConfig.SubjectAltDns = TEST_SERVER_HOSTNAME; - serverConfig.IsCa = 0; - serverConfig.Issuer = &trustedCa; - MbedTlsTestCert_Create(&serverConfig, &serverCert, &rng); - } - - void teardown() override - { - /* tlsStream first: its Destroy drives the transport Close that closes - * the current client fd, so it must run before the transport is freed. */ - if (tlsStream != nullptr) - { - SolidSyslogMbedTlsStream_Destroy(tlsStream); - } - if (transport != nullptr) - { - ReconnectingSocketStream_Destroy(transport); - } - if (server != nullptr) - { - MbedTlsResumptionServer_Destroy(server); - } - MbedTlsTestCert_Destroy(&serverCert); - MbedTlsTestCert_Destroy(&trustedCa); - mbedtls_ctr_drbg_free(&rng); - mbedtls_entropy_free(&entropy); - } - - struct SolidSyslogMbedTlsStreamConfig BuildBaseConfig(struct SolidSyslogStream* clientTransport) - { - struct SolidSyslogMbedTlsStreamConfig cfg = {}; - cfg.Transport = clientTransport; - cfg.Sleep = NoOpSleep; - cfg.Rng = &rng; - cfg.CaChain = &trustedCa.Cert; - cfg.ServerName = TEST_SERVER_HOSTNAME; - return cfg; - } - - struct MbedTlsResumptionServer* StartServer(bool rotateTicketKey) - { - struct MbedTlsResumptionServerConfig serverConfig = {}; - serverConfig.ServerFds[0] = serverFds[0]; - serverConfig.ServerFds[1] = serverFds[1]; - serverConfig.ServerCert = &serverCert; - serverConfig.Rng = &rng; - serverConfig.RotateTicketKeyBetweenConnections = rotateTicketKey; - return MbedTlsResumptionServer_Create(&serverConfig); - } - - /* One Open -> Send -> (the test closes if reconnecting) cycle on the same - * stream. Returns whether the Open and Send both succeeded. */ - bool ConnectAndDeliver(const char* payload) const - { - bool opened = SolidSyslogStream_Open(tlsStream, addr); - bool sent = opened && SolidSyslogStream_Send(tlsStream, payload, 8U); - return opened && sent; - } -}; - -// clang-format on - -TEST(SolidSyslogMbedTlsStreamResumption, SameStreamReconnectResumesTheTlsSession) - -{ - transport = ReconnectingSocketStream_Create(clientFds, 2); - struct SolidSyslogMbedTlsStreamConfig config = BuildBaseConfig(transport); - tlsStream = SolidSyslogMbedTlsStream_Create(&config); - server = StartServer(/*rotateTicketKey=*/false); - - CHECK_TRUE_TEXT(ConnectAndDeliver("<13>one\n"), "first (full) handshake + send should succeed"); - SolidSyslogStream_Close(tlsStream); /* the fail-fast reconnect the library performs in production */ - CHECK_TRUE_TEXT(ConnectAndDeliver("<13>two\n"), "second (resumed) handshake + send should succeed"); - - MbedTlsResumptionServer_Join(server); - CHECK_TRUE_TEXT( - MbedTlsResumptionServer_BothHandshakesSucceeded(server), - "both server-side handshakes should complete" - ); - CHECK_TRUE_TEXT(MbedTlsResumptionServer_BothMessagesDelivered(server), "a record should arrive on each connection"); - CHECK_TRUE_TEXT( - MbedTlsResumptionServer_SecondHandshakeResumed(server), - "the second handshake must present a ticket the server accepts (abbreviated handshake)" - ); -} - -TEST(SolidSyslogMbedTlsStreamResumption, NonResumingPeerFallsBackToFullHandshakeAndStillDelivers) - -{ - transport = ReconnectingSocketStream_Create(clientFds, 2); - struct SolidSyslogMbedTlsStreamConfig config = BuildBaseConfig(transport); - tlsStream = SolidSyslogMbedTlsStream_Create(&config); - /* Rotate the ticket key between connections so the client's saved ticket - * no longer decrypts — the resume offer is rejected. */ - server = StartServer(/*rotateTicketKey=*/true); - - CHECK_TRUE_TEXT(ConnectAndDeliver("<13>one\n"), "first handshake + send should succeed"); - SolidSyslogStream_Close(tlsStream); - CHECK_TRUE_TEXT(ConnectAndDeliver("<13>two\n"), "second handshake + send should still succeed"); - - MbedTlsResumptionServer_Join(server); - CHECK_TRUE_TEXT(MbedTlsResumptionServer_BothHandshakesSucceeded(server), "both handshakes should complete"); - CHECK_TRUE_TEXT( - MbedTlsResumptionServer_BothMessagesDelivered(server), - "delivery must survive the full-handshake fallback" - ); - CHECK_FALSE_TEXT( - MbedTlsResumptionServer_SecondHandshakeResumed(server), - "with a rotated ticket key the second handshake must NOT resume" - ); -} diff --git a/Tests/Support/MbedTlsFake.c b/Tests/Support/MbedTlsFake.c index 9f48e463..5d4847ee 100644 --- a/Tests/Support/MbedTlsFake.c +++ b/Tests/Support/MbedTlsFake.c @@ -112,27 +112,6 @@ static mbedtls_ssl_config* lastSslConfOwnCertConfigArg; static mbedtls_x509_crt* lastSslConfOwnCertCertArg; static mbedtls_pk_context* lastSslConfOwnCertKeyArg; -/* mbedtls_ssl_session_init */ -static int sslSessionInitCallCount; -static mbedtls_ssl_session* lastSslSessionInitArg; - -/* mbedtls_ssl_get_session */ -static int sslGetSessionCallCount; -static const mbedtls_ssl_context* lastSslGetSessionContextArg; -static mbedtls_ssl_session* lastSslGetSessionSessionArg; -static int sslGetSessionReturn; - -/* mbedtls_ssl_set_session */ -static int sslSetSessionCallCount; -static mbedtls_ssl_context* lastSslSetSessionContextArg; -static const mbedtls_ssl_session* lastSslSetSessionSessionArg; -static int sslSetSessionReturn; -static int sslSetSessionHandshakeCountAtCall; - -/* mbedtls_ssl_session_free */ -static int sslSessionFreeCallCount; -static mbedtls_ssl_session* lastSslSessionFreeArg; - /* ------------------------------------------------------------------------- * Test accessors. * ------------------------------------------------------------------------- */ @@ -199,19 +178,6 @@ void MbedTlsFake_Reset(void) lastSslConfOwnCertConfigArg = NULL; lastSslConfOwnCertCertArg = NULL; lastSslConfOwnCertKeyArg = NULL; - sslSessionInitCallCount = 0; - lastSslSessionInitArg = NULL; - sslGetSessionCallCount = 0; - lastSslGetSessionContextArg = NULL; - lastSslGetSessionSessionArg = NULL; - sslGetSessionReturn = 0; - sslSetSessionCallCount = 0; - lastSslSetSessionContextArg = NULL; - lastSslSetSessionSessionArg = NULL; - sslSetSessionReturn = 0; - sslSetSessionHandshakeCountAtCall = 0; - sslSessionFreeCallCount = 0; - lastSslSessionFreeArg = NULL; } int MbedTlsFake_SslConfigInitCallCount(void) @@ -515,71 +481,6 @@ mbedtls_pk_context* MbedTlsFake_LastSslConfOwnCertKeyArg(void) return lastSslConfOwnCertKeyArg; } -int MbedTlsFake_SslSessionInitCallCount(void) -{ - return sslSessionInitCallCount; -} - -mbedtls_ssl_session* MbedTlsFake_LastSslSessionInitArg(void) -{ - return lastSslSessionInitArg; -} - -int MbedTlsFake_SslGetSessionCallCount(void) -{ - return sslGetSessionCallCount; -} - -const mbedtls_ssl_context* MbedTlsFake_LastSslGetSessionContextArg(void) -{ - return lastSslGetSessionContextArg; -} - -mbedtls_ssl_session* MbedTlsFake_LastSslGetSessionSessionArg(void) -{ - return lastSslGetSessionSessionArg; -} - -void MbedTlsFake_SetSslGetSessionReturn(int value) -{ - sslGetSessionReturn = value; -} - -int MbedTlsFake_SslSetSessionCallCount(void) -{ - return sslSetSessionCallCount; -} - -mbedtls_ssl_context* MbedTlsFake_LastSslSetSessionContextArg(void) -{ - return lastSslSetSessionContextArg; -} - -const mbedtls_ssl_session* MbedTlsFake_LastSslSetSessionSessionArg(void) -{ - return lastSslSetSessionSessionArg; -} - -void MbedTlsFake_SetSslSetSessionReturn(int value) -{ - sslSetSessionReturn = value; -} - -int MbedTlsFake_SslSetSessionHandshakeCountAtCall(void) -{ - return sslSetSessionHandshakeCountAtCall; -} - -int MbedTlsFake_SslSessionFreeCallCount(void) -{ - return sslSessionFreeCallCount; -} - -mbedtls_ssl_session* MbedTlsFake_LastSslSessionFreeArg(void) -{ - return lastSslSessionFreeArg; -} - /* ------------------------------------------------------------------------- * Link-interposed mbedTLS symbols. The test executable does not link * libmbedtls; the production code's calls to mbedtls_* resolve here. @@ -736,32 +637,3 @@ int mbedtls_ssl_set_hostname(mbedtls_ssl_context* ssl, const char* hostname) lastSslSetHostnameNameArg = hostname; return sslSetHostnameReturn; } - -void mbedtls_ssl_session_init(mbedtls_ssl_session* session) -{ - sslSessionInitCallCount++; - lastSslSessionInitArg = session; -} - -int mbedtls_ssl_get_session(const mbedtls_ssl_context* ssl, mbedtls_ssl_session* session) -{ - sslGetSessionCallCount++; - lastSslGetSessionContextArg = ssl; - lastSslGetSessionSessionArg = session; - return sslGetSessionReturn; -} - -int mbedtls_ssl_set_session(mbedtls_ssl_context* ssl, const mbedtls_ssl_session* session) -{ - sslSetSessionCallCount++; - lastSslSetSessionContextArg = ssl; - lastSslSetSessionSessionArg = session; - sslSetSessionHandshakeCountAtCall = sslHandshakeCallCount; - return sslSetSessionReturn; -} - -void mbedtls_ssl_session_free(mbedtls_ssl_session* session) -{ - sslSessionFreeCallCount++; - lastSslSessionFreeArg = session; -} diff --git a/Tests/Support/MbedTlsFake.h b/Tests/Support/MbedTlsFake.h index da397ebf..c8aa0398 100644 --- a/Tests/Support/MbedTlsFake.h +++ b/Tests/Support/MbedTlsFake.h @@ -8,7 +8,6 @@ struct mbedtls_ssl_config; struct mbedtls_ssl_context; -struct mbedtls_ssl_session; struct mbedtls_x509_crt; struct mbedtls_x509_crl; struct mbedtls_ctr_drbg_context; @@ -108,29 +107,6 @@ EXTERN_C_BEGIN const char* MbedTlsFake_LastSslSetHostnameNameArg(void); void MbedTlsFake_SetSslSetHostnameReturn(int value); - /* mbedtls_ssl_session_init (session-resumption save slot) */ - int MbedTlsFake_SslSessionInitCallCount(void); - struct mbedtls_ssl_session* MbedTlsFake_LastSslSessionInitArg(void); - - /* mbedtls_ssl_get_session (capture negotiated session after handshake) */ - int MbedTlsFake_SslGetSessionCallCount(void); - const struct mbedtls_ssl_context* MbedTlsFake_LastSslGetSessionContextArg(void); - struct mbedtls_ssl_session* MbedTlsFake_LastSslGetSessionSessionArg(void); - void MbedTlsFake_SetSslGetSessionReturn(int value); - - /* mbedtls_ssl_set_session (feed saved session back before next handshake) */ - int MbedTlsFake_SslSetSessionCallCount(void); - struct mbedtls_ssl_context* MbedTlsFake_LastSslSetSessionContextArg(void); - const struct mbedtls_ssl_session* MbedTlsFake_LastSslSetSessionSessionArg(void); - void MbedTlsFake_SetSslSetSessionReturn(int value); - /* Snapshot of the handshake call count at the moment set_session ran — - * lets a test prove the restore happens before the handshake it primes. */ - int MbedTlsFake_SslSetSessionHandshakeCountAtCall(void); - - /* mbedtls_ssl_session_free (release saved session on Destroy / recapture) */ - int MbedTlsFake_SslSessionFreeCallCount(void); - struct mbedtls_ssl_session* MbedTlsFake_LastSslSessionFreeArg(void); - /* mbedtls_ssl_conf_own_cert (mTLS client identity wiring) */ int MbedTlsFake_SslConfOwnCertCallCount(void); struct mbedtls_ssl_config* MbedTlsFake_LastSslConfOwnCertConfigArg(void); diff --git a/docs/integrating-mbedtls.md b/docs/integrating-mbedtls.md index 655d8cc3..f340a210 100644 --- a/docs/integrating-mbedtls.md +++ b/docs/integrating-mbedtls.md @@ -195,41 +195,6 @@ shows the minimal config that satisfies the above for QEMU mps2-an385. --- -## Session resumption - -Under the fail-fast reconnect model, every transient peer hiccup tears the -TLS connection down and the next `Open` does a fresh handshake. A full -handshake costs ~2 RTTs plus the asymmetric crypto on both sides — the -dominant cost of the reconnect loop on a constrained target. - -The adapter resumes the TLS session across reconnects of the **same** -`SolidSyslogMbedTlsStream` instance. After a successful handshake it -captures the negotiated session (`mbedtls_ssl_get_session`) and feeds it -back (`mbedtls_ssl_set_session`) before the next handshake, so the peer can -issue an abbreviated handshake (~1 RTT, no asymmetric crypto). One session -is held per stream; the adapter does not multiplex destinations inside a -single stream. The saved session lives for the life of the instance and is -freed on `Destroy`. - -**Integrator requirement.** Resumption needs client-side session tickets -compiled into your Mbed TLS config: - -```c -#define MBEDTLS_SSL_SESSION_TICKETS /* client-side ticket storage */ -``` - -This is enabled by default in the upstream `mbedtls_config.h` (and in the -3.6.x tree used by the `integration-linux-mbedtls` check). If your trimmed -embedded config leaves it undefined, the adapter still works — resumption -simply never engages and every connect is a full handshake. - -**Best-effort, never a delivery precondition.** No saved session yet, a -peer that offers no ticket, or an expired / evicted ticket all fall back to -a full handshake transparently; the message is still delivered. Resumption -only ever makes the handshake cheaper — it never blocks the send path. - ---- - ## Reference integrations | Target | Adapter source | Mbed TLS config | Notes | diff --git a/misra_suppressions.txt b/misra_suppressions.txt index 59ce38e1..6654ca87 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -53,7 +53,7 @@ misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpDatagram.c:49 misra-c2012-11.3:Platform/FreeRtos/Source/SolidSyslogFreeRtosMutex.c:40 misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpResolver.c:44 misra-c2012-11.3:Platform/PlusTcp/Source/SolidSyslogPlusTcpTcpStream.c:114 -misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:102 +misra-c2012-11.3:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:94 misra-c2012-11.3:Platform/OpenSsl/Source/SolidSyslogTlsStream.c:82 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddress.c:10 misra-c2012-11.3:Platform/Posix/Source/SolidSyslogPosixAddressPrivate.h:19 @@ -81,8 +81,8 @@ misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDnsResolver.c:145 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawDnsResolver.c:207 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:139 misra-c2012-11.5:Platform/LwipRaw/Source/SolidSyslogLwipRawTcpStream.c:147 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:315 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:327 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:271 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:283 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockDatagram.c:138 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:353 misra-c2012-11.5:Platform/Windows/Source/SolidSyslogWinsockTcpStream.c:373 @@ -183,5 +183,5 @@ misra-c2012-8.9:Core/Source/SolidSyslogFileBlockDevice.c:16 # D.013 — Rule 11.5: void* ↔ unsigned char* at third-party byte-buffer API boundaries # See docs/misra-deviations.md#d013 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:352 -misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:371 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:308 +misra-c2012-11.5:Platform/MbedTls/Source/SolidSyslogMbedTlsStream.c:327 From f919179f91b346cd7f267d3260df9f672b3037b3 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 31 May 2026 08:33:14 +0000 Subject: [PATCH 2/2] docs: record TLS-resumption out-of-scope decision in DEVLOG The revert itself removes S26.04's DEVLOG entry; this adds an entry capturing the cross-backend decision (revert mbedTLS S26.04, won't-do OpenSSL S26.01) with the architectural rationale and the hand-rolled-test-server lesson, so the direction isn't re-proposed. Co-Authored-By: Claude Opus 4.8 (1M context) --- DEVLOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/DEVLOG.md b/DEVLOG.md index a84a0e98..eba65517 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,55 @@ # Dev Log +## 2026-05-31 — Drop TLS session resumption from both reference integrations (revert S26.04, won't-do S26.01) + +Decision session, not a feature. TLS session resumption is now **out of scope** +for both reference TLS integrations (`Platform/OpenSsl/`, `Platform/MbedTls/`), +on either backend, for either TLS 1.2 or TLS 1.3. This commit reverts the merged +mbedTLS work (S26.04, #489) and the OpenSSL story (S26.01, #280) is closed +won't-do. + +### Decision + +- **mbedTLS (S26.04, #489, already on main):** reverted here via a clean + single-parent `git revert d37ef31`. The production diff was purely additive + (42 add / 0 del) with no bundled bug fix to preserve, so the revert is total. +- **OpenSSL (S26.01, #280):** won't-do. The in-progress slices 1–5 are dropped + (abandoned `feat/s26-01-openssl-session-resumption` branch). The one genuine + independent bug found along the way — `SSL_CTX` leaked on `Close` under the + fail-fast reconnect cycle — is kept and lands as its own PR + (`fix/openssl-tlsstream-ctx-leak`). + +### Why (architectural rationale) + +1. **Resumption is an optimization, never correctness.** A full handshake must + always succeed on every (re)connect without disrupting core operation; + products must be architected for that regardless. +2. **Long-lived connections make it near-worthless here.** The TCP streams use + keepalive — a syslog client does one handshake then streams for a long time. + Resumption only pays at reconnect, and reconnects are rare. +3. **When reconnects DO happen, resumption is least likely to be available.** A + reconnect is usually triggered by a disgraceful close — exactly when the + server has discarded the state needed to resume. The optimization tends to be + absent in the very case it was meant to help. +4. **Disproportionate cost/complexity to do honestly.** TLS 1.2 (RFC 5077) and + 1.3 (RFC 8446 PSK) use different mechanisms; 1.3 tickets arrive post-handshake + and need a defined read to capture; mTLS-on-resume has separate identity + semantics. Four real cases ({1.2,1.3} × {server-auth, mTLS}), each needing an + honest mechanism-correct test — a large surface for an optional speedup. + +TLS 1.3 0-RTT / early data was already out of scope and stays out. + +### Test-strategy lesson (if ever revisited) + +Don't hand-roll a TLS server in the harness — it encodes our own fallible +understanding of resumption semantics, so a failing test can't distinguish a +client bug from a server-config bug. This cost real time. Prefer driving the +**real sender stack** against an **off-the-shelf** server (`openssl s_server` / +stunnel), observed **server-side** — one backend-neutral server covers both +OpenSSL and mbedTLS clients and all four cases via flags. That is process-level +(real port), so it belongs in the BDD/compose layer, not the in-process CppUTest +binary. + ## 2026-05-30 — S28.08 SolidSyslogLwipRawDnsResolver (DNS via dns_gethostbyname) + FreeRtosLwip BDD by-name Last implementation story of E28. Adds the DNS sibling of the numeric