Branch data Line data Source code
1 : : /* SPDX-FileCopyrightText: Copyright 2026 Cozens Software Solutions Limited
2 : : * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial
3 : : */
4 : :
5 : : #include "SolidSyslogMbedTlsStream.h"
6 : :
7 : : #include <mbedtls/ctr_drbg.h>
8 : : #include <mbedtls/md.h>
9 : : #include <mbedtls/x509_crt.h>
10 : : #include <mbedtls/ssl.h>
11 : : #include <mbedtls/pk.h>
12 : : #include <mbedtls/x509.h>
13 : : #include <stdbool.h>
14 : : #include <stddef.h>
15 : : #include <stdint.h>
16 : :
17 : : #include "SolidSyslogError.h"
18 : : #include "SolidSyslogErrorCategory.h"
19 : : #include "SolidSyslogMbedTlsCredentialsDefinition.h"
20 : : #include "SolidSyslogMbedTlsStreamErrors.h"
21 : : #include "SolidSyslogMbedTlsStreamPrivate.h"
22 : :
23 : : /* Mbed TLS compiles its notBefore / notAfter checks out entirely without this,
24 : : * on VERIFY_REQUIRED and VERIFY_OPTIONAL alike - so no certificate in the chain
25 : : * is date-checked and nothing says so at runtime. The TLS contract states that a
26 : : * pin does not extend a certificate's validity period, which is a promise that
27 : : * would then silently not hold, so the build stops here rather than shipping it.
28 : : *
29 : : * A target with no clock cannot check dates and is a legitimate deployment.
30 : : * Define SOLIDSYSLOG_MBEDTLS_NO_VALIDITY_CHECK to say so deliberately; the
31 : : * obligation is then knowingly unmet rather than quietly missing. */
32 : : #if !defined(MBEDTLS_HAVE_TIME_DATE) && !defined(SOLIDSYSLOG_MBEDTLS_NO_VALIDITY_CHECK)
33 : : #error \
34 : : "MBEDTLS_HAVE_TIME_DATE is off, so certificate validity is not checked. Enable it, or define SOLIDSYSLOG_MBEDTLS_NO_VALIDITY_CHECK to accept that."
35 : : #endif
36 : : #include "SolidSyslogNullStream.h"
37 : : #include "SolidSyslogPrival.h"
38 : : #include "SolidSyslogStream.h"
39 : : #include "SolidSyslogStreamDefinition.h"
40 : : #include "SolidSyslogTlsCredentialsInstalled.h"
41 : : #include "SolidSyslogTlsFingerprint.h"
42 : : #include "SolidSyslogTlsStreamCategories.h"
43 : : #include "SolidSyslogTunables.h"
44 : :
45 : : const struct SolidSyslogErrorSource SolidSyslogMbedTlsStreamErrorSource = {
46 : : "MbedTlsStream",
47 : : SolidSyslogTlsStreamErrors_Name
48 : : };
49 : :
50 : : enum
51 : : {
52 : : HANDSHAKE_POLL_INTERVAL_MILLISECONDS = 10U,
53 : : MBEDTLS_STREAM_DHM_MIN_BITLEN = 2048U,
54 : : MBEDTLS_STREAM_DISCARD_BYTES = 16U,
55 : : MBEDTLS_STREAM_MAX_READS_BEFORE_WRITE = 8U
56 : : };
57 : :
58 : : struct SolidSyslogAddress;
59 : :
60 : : static uint32_t MbedTlsStream_NullHandshakeTimeoutGetter(void* context);
61 : : static uint32_t MbedTlsStream_NullVersion(void* context);
62 : : static void MbedTlsStream_NullProfile(struct SolidSyslogMbedTlsProfile* profile, void* context);
63 : : static inline void MbedTlsStream_PullProfile(struct SolidSyslogMbedTlsStream* self);
64 : : static inline void MbedTlsStream_ApplyCipherPolicy(struct SolidSyslogMbedTlsStream* self);
65 : : static inline void MbedTlsStream_ApplyCertProfilePolicy(struct SolidSyslogMbedTlsStream* self);
66 : : static inline bool MbedTlsStream_ConfigProvidesHandshakeGetter(const struct SolidSyslogMbedTlsStreamConfig* config);
67 : : static inline bool MbedTlsStream_ConfigProvidesVersion(const struct SolidSyslogMbedTlsStreamConfig* config);
68 : : static inline bool MbedTlsStream_ConfigProvidesProfile(const struct SolidSyslogMbedTlsStreamConfig* config);
69 : : static inline uint32_t MbedTlsStream_ResolveHandshakeTimeoutMs(struct SolidSyslogMbedTlsStream* self);
70 : : static inline struct SolidSyslogMbedTlsStream* MbedTlsStream_SelfFromBase(struct SolidSyslogStream* base);
71 : : static inline bool MbedTlsStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr);
72 : : static inline bool MbedTlsStream_IsOpen(const struct SolidSyslogMbedTlsStream* self);
73 : : static inline bool MbedTlsStream_Establish(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr);
74 : : static inline bool MbedTlsStream_ApplySslConfigDefaults(struct SolidSyslogMbedTlsStream* self);
75 : : static inline void MbedTlsStream_ApplyTlsPolicy(struct SolidSyslogMbedTlsStream* self);
76 : : static inline bool MbedTlsStream_InstallCredentials(struct SolidSyslogMbedTlsStream* self);
77 : : static inline bool MbedTlsStream_PeerIsAuthorisable(const struct SolidSyslogTlsCredentialsInstalled* installed);
78 : : static inline bool MbedTlsStream_FingerprintsAreUsable(struct SolidSyslogMbedTlsStream* self);
79 : : static inline void MbedTlsStream_ApplyPeerVerificationPolicy(struct SolidSyslogMbedTlsStream* self);
80 : : static int MbedTlsStream_VerifyPeer(void* context, mbedtls_x509_crt* crt, int depth, uint32_t* flags);
81 : : static inline int MbedTlsStream_EnforceWhereTheLibraryWillNot(
82 : : struct SolidSyslogMbedTlsStream* self,
83 : : int depth,
84 : : const uint32_t* flags
85 : : );
86 : : static inline uint32_t MbedTlsStream_ChainTrustFlags(void);
87 : : static inline bool MbedTlsStream_LeafMatchesAPin(struct SolidSyslogMbedTlsStream* self, mbedtls_x509_crt* leaf);
88 : : static inline mbedtls_md_type_t MbedTlsStream_MdTypeFor(enum SolidSyslogTlsHashAlgorithm algorithm);
89 : : static bool MbedTlsStream_DigestCertificate(
90 : : void* context,
91 : : enum SolidSyslogTlsHashAlgorithm algorithm,
92 : : uint8_t* digest,
93 : : size_t* length
94 : : );
95 : : static inline void MbedTlsStream_ReleaseCredentials(struct SolidSyslogMbedTlsStream* self);
96 : : static inline bool MbedTlsStream_BindContextToConfig(struct SolidSyslogMbedTlsStream* self);
97 : : static inline bool MbedTlsStream_ConfigureExpectedHostname(struct SolidSyslogMbedTlsStream* self);
98 : : static inline bool MbedTlsStream_NameBeginsWithADot(const char* name);
99 : : static inline void MbedTlsStream_DeclareNoPeerName(struct SolidSyslogMbedTlsStream* self);
100 : : static inline void MbedTlsStream_InstallTransportCallbacks(struct SolidSyslogMbedTlsStream* self);
101 : : static inline bool MbedTlsStream_PerformHandshake(struct SolidSyslogMbedTlsStream* self);
102 : : static inline bool MbedTlsStream_PeerPassedVerification(struct SolidSyslogMbedTlsStream* self);
103 : : static inline enum SolidSyslogTlsStreamErrors MbedTlsStream_RefusalDetail(struct SolidSyslogMbedTlsStream* self);
104 : : static inline bool MbedTlsStream_IsVerifyFailure(uint32_t verdict);
105 : : static inline enum SolidSyslogTlsStreamErrors MbedTlsStream_DetailForVerifyFailure(uint32_t verdict);
106 : : static inline bool MbedTlsStream_HasUnnamedVerifyFailure(uint32_t verdict);
107 : : static inline bool MbedTlsStream_IsRetryableHandshakeRc(int rc);
108 : : static inline enum SolidSyslogTlsStreamErrors MbedTlsStream_InitDetail(
109 : : int rc,
110 : : enum SolidSyslogTlsStreamErrors otherwise
111 : : );
112 : : static inline bool MbedTlsStream_IsAllocationFailure(int rc);
113 : : static inline bool MbedTlsStream_IsHandshakeBudgetExhausted(uint32_t totalSleptMs, uint32_t budgetMs);
114 : : static inline uint32_t MbedTlsStream_HandshakePollMs(uint32_t totalSleptMs, uint32_t budgetMs);
115 : : static inline bool MbedTlsStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size);
116 : : static bool MbedTlsStream_SessionIsStillOpen(struct SolidSyslogStream* base);
117 : : static inline SolidSyslogSsize MbedTlsStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size);
118 : : static inline void MbedTlsStream_Close(struct SolidSyslogStream* base);
119 : : static uint32_t MbedTlsStream_Version(struct SolidSyslogStream* base);
120 : : static int MbedTlsStream_BioSend(void* ctx, const unsigned char* buf, size_t len);
121 : : static int MbedTlsStream_BioRecv(void* ctx, unsigned char* buf, size_t len);
122 : :
123 : 217 : void SolidSyslogMbedTlsStream_Initialise(
124 : : struct SolidSyslogStream* base,
125 : : const struct SolidSyslogMbedTlsStreamConfig* config
126 : : )
127 : : {
128 : 217 : struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base);
129 : 217 : self->Base.Open = MbedTlsStream_Open;
130 : 217 : self->Base.Send = MbedTlsStream_Send;
131 : 217 : self->Base.Read = MbedTlsStream_Read;
132 : 217 : self->Base.Close = MbedTlsStream_Close;
133 : 217 : self->Base.Version = MbedTlsStream_Version;
134 : 217 : self->Config = *config;
135 : 217 : self->CredentialsInstalled = false;
136 : 217 : self->OpenHealth = (struct SolidSyslogStreamOpenHealth) {0};
137 [ + + ]: 217 : if (MbedTlsStream_ConfigProvidesHandshakeGetter(config) == false)
138 : : {
139 : : /* Substitute the Null Object so the bounded-handshake loop has a
140 : : * single code path regardless of whether the integrator wired
141 : : * runtime tuning. */
142 : 213 : self->Config.GetHandshakeTimeoutMs = MbedTlsStream_NullHandshakeTimeoutGetter;
143 : 213 : self->Config.HandshakeTimeoutContext = NULL;
144 : : }
145 [ + + ]: 217 : if (MbedTlsStream_ConfigProvidesVersion(config) == false)
146 : : {
147 : 215 : self->Config.Version = MbedTlsStream_NullVersion;
148 : 215 : self->Config.VersionContext = NULL;
149 : : }
150 [ + + ]: 217 : if (MbedTlsStream_ConfigProvidesProfile(config) == false)
151 : : {
152 : 9 : self->Config.Profile = MbedTlsStream_NullProfile;
153 : 9 : self->Config.ProfileContext = NULL;
154 : : }
155 : : /* Eager init so mbedtls_*_free in Close is always safe - whether Open
156 : : * was ever reached, whether it succeeded, or whether Close is being
157 : : * called twice in a row. mbedTLS guarantees a freed struct is left in
158 : : * the same zeroed state an init produces, so re-Open after Close also
159 : : * works without re-init. */
160 : 217 : mbedtls_ssl_init(&self->SslContext);
161 : 217 : mbedtls_ssl_config_init(&self->SslConfig);
162 : 217 : }
163 : :
164 : : /* Null Object substituted in Initialise when the integrator does not install a
165 : : * getter - returns the compile-time tunable so the bounded-handshake path is a
166 : : * single code path regardless of whether the integrator wired runtime tuning. */
167 : 118 : static uint32_t MbedTlsStream_NullHandshakeTimeoutGetter(void* context)
168 : : {
169 : : (void) context;
170 : 118 : return (uint32_t) SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS;
171 : : }
172 : :
173 : 217 : static inline bool MbedTlsStream_ConfigProvidesHandshakeGetter(const struct SolidSyslogMbedTlsStreamConfig* config)
174 : : {
175 : 217 : return config->GetHandshakeTimeoutMs != NULL;
176 : : }
177 : :
178 : : /* Null Object substituted at Initialise when the integrator installs no version
179 : : * function - reports an unchanging configuration, so the sender never reconnects
180 : : * on this stream's account. */
181 : 1 : static uint32_t MbedTlsStream_NullVersion(void* context)
182 : : {
183 : : (void) context;
184 : 1 : return 0U;
185 : : }
186 : :
187 : 217 : static inline bool MbedTlsStream_ConfigProvidesVersion(const struct SolidSyslogMbedTlsStreamConfig* config)
188 : : {
189 : 217 : return config->Version != NULL;
190 : : }
191 : :
192 : : /* Null Object substituted at Initialise when the integrator supplies no profile -
193 : : * leaves every field at the library default. */
194 : 1 : static void MbedTlsStream_NullProfile(struct SolidSyslogMbedTlsProfile* profile, void* context)
195 : : {
196 : : (void) context;
197 : : (void) profile;
198 : 1 : }
199 : :
200 : 217 : static inline bool MbedTlsStream_ConfigProvidesProfile(const struct SolidSyslogMbedTlsStreamConfig* config)
201 : : {
202 : 217 : return config->Profile != NULL;
203 : : }
204 : :
205 : : /* Bridges the integrator-installed getter (or the Null Object substituted at
206 : : * config-copy time) to the bounded handshake deadline. Invoked at the start
207 : : * of each handshake attempt so runtime-tunable values take effect on the next
208 : : * reconnect. */
209 : 122 : static inline uint32_t MbedTlsStream_ResolveHandshakeTimeoutMs(struct SolidSyslogMbedTlsStream* self)
210 : : {
211 : 122 : return self->Config.GetHandshakeTimeoutMs(self->Config.HandshakeTimeoutContext);
212 : : }
213 : :
214 : 911 : static inline struct SolidSyslogMbedTlsStream* MbedTlsStream_SelfFromBase(struct SolidSyslogStream* base)
215 : : {
216 : 911 : return (struct SolidSyslogMbedTlsStream*) base;
217 : : }
218 : :
219 : 217 : void SolidSyslogMbedTlsStream_Cleanup(struct SolidSyslogStream* base)
220 : : {
221 : : /* An integrator who destroys a still-Open stream must not leak the
222 : : * underlying TLS state. */
223 : 217 : MbedTlsStream_Close(base);
224 : : /* Overwrite the abstract base with the shared NullStream vtable so
225 : : * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */
226 : 217 : *base = *SolidSyslogNullStream_Get();
227 : 217 : }
228 : :
229 : : /* Idempotent: a previous Close left the structs in mbedTLS's freed-equivalent
230 : : * (zeroed) state, so close_notify sees conf == NULL and returns harmlessly,
231 : : * and the *_free calls are no-ops on already-freed structs. Transport Close
232 : : * is itself idempotent on every Stream impl. */
233 : 324 : static inline void MbedTlsStream_Close(struct SolidSyslogStream* base)
234 : : {
235 : 324 : struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base);
236 : 324 : (void) mbedtls_ssl_close_notify(&self->SslContext);
237 : 324 : mbedtls_ssl_free(&self->SslContext);
238 : : /* The ssl_config holds the caller's certificates in its key_cert nodes until
239 : : it is freed, so the credentials are told the window has closed only after
240 : : mbedTLS has let go of them. */
241 : 324 : mbedtls_ssl_config_free(&self->SslConfig);
242 : 324 : MbedTlsStream_ReleaseCredentials(self);
243 : 324 : SolidSyslogStream_Close(self->Config.Transport);
244 : 324 : }
245 : :
246 : 3 : static uint32_t MbedTlsStream_Version(struct SolidSyslogStream* base)
247 : : {
248 : 3 : struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base);
249 : 3 : return self->Config.Version(self->Config.VersionContext);
250 : : }
251 : :
252 : 161 : static inline bool MbedTlsStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr)
253 : : {
254 : 161 : struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base);
255 : 161 : bool ok = false;
256 [ + + ]: 161 : if (MbedTlsStream_IsOpen(self))
257 : : {
258 : : /* Refused by contract, not reconnected: what is held stays as it is. */
259 : 2 : MbedTlsStream_Report(
260 : : SOLIDSYSLOG_BAD_ARGUMENT_SEVERITY,
261 : : SOLIDSYSLOG_CAT_BAD_ARGUMENT,
262 : : SOLIDSYSLOG_TLS_STREAM_ERROR_ALREADY_OPEN
263 : : );
264 : : }
265 : : else
266 : : {
267 : 159 : ok = MbedTlsStream_Establish(base, addr);
268 [ + + ]: 159 : if (ok)
269 : : {
270 : 75 : SolidSyslogStreamOpenHealth_Opened(&self->OpenHealth);
271 : : }
272 : : }
273 : 161 : return ok;
274 : : }
275 : :
276 : : /* Every Open that succeeds installs the credentials and every Close releases
277 : : * them, so holding them is the one mark of a session still open. */
278 : 161 : static inline bool MbedTlsStream_IsOpen(const struct SolidSyslogMbedTlsStream* self)
279 : : {
280 : 161 : return self->CredentialsInstalled;
281 : : }
282 : :
283 : 159 : static inline bool MbedTlsStream_Establish(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr)
284 : : {
285 : 159 : struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base);
286 : 159 : MbedTlsStream_PullProfile(self);
287 : 159 : self->RefusedVerdict = 0U;
288 : 159 : self->PinVerdict = SOLIDSYSLOG_TLS_AUTHORISATION_NO_MATCH;
289 [ + + + + ]: 159 : bool ok = SolidSyslogStream_Open(self->Config.Transport, addr) && MbedTlsStream_ApplySslConfigDefaults(self);
290 [ + + ]: 159 : if (ok)
291 : : {
292 : 152 : MbedTlsStream_ApplyTlsPolicy(self);
293 : 152 : MbedTlsStream_ApplyCipherPolicy(self);
294 : 152 : MbedTlsStream_ApplyCertProfilePolicy(self);
295 [ + + + + : 281 : ok = MbedTlsStream_InstallCredentials(self) && MbedTlsStream_BindContextToConfig(self) &&
+ + ]
296 : 129 : MbedTlsStream_ConfigureExpectedHostname(self);
297 : : }
298 [ + + ]: 159 : if (ok)
299 : : {
300 : 122 : MbedTlsStream_InstallTransportCallbacks(self);
301 [ + + + + ]: 122 : ok = MbedTlsStream_PerformHandshake(self) && MbedTlsStream_PeerPassedVerification(self);
302 : : }
303 [ + + ]: 159 : if (!ok)
304 : : {
305 : 84 : MbedTlsStream_Close(base);
306 : : }
307 : 159 : return ok;
308 : : }
309 : :
310 : : /* One snapshot per connection: every later step reads the same answer, however
311 : : * the integrator's own state moves while the handshake is in progress. */
312 : 159 : static inline void MbedTlsStream_PullProfile(struct SolidSyslogMbedTlsStream* self)
313 : : {
314 : 159 : self->Profile = (struct SolidSyslogMbedTlsProfile) {0};
315 : 159 : self->Config.Profile(&self->Profile, self->Config.ProfileContext);
316 : 159 : }
317 : :
318 : 157 : static inline bool MbedTlsStream_ApplySslConfigDefaults(struct SolidSyslogMbedTlsStream* self)
319 : : {
320 : 157 : int rc = mbedtls_ssl_config_defaults(
321 : : &self->SslConfig,
322 : : MBEDTLS_SSL_IS_CLIENT,
323 : : MBEDTLS_SSL_TRANSPORT_STREAM,
324 : : MBEDTLS_SSL_PRESET_DEFAULT
325 : : );
326 : 157 : bool ok = rc == 0;
327 [ + + ]: 157 : if (!ok)
328 : : {
329 : 5 : MbedTlsStream_ReportOpenFailure(
330 : : self,
331 : : SOLIDSYSLOG_SEVERITY_ERROR,
332 : : SOLIDSYSLOG_CAT_TLS_STREAM_INIT_FAILED,
333 : : MbedTlsStream_InitDetail(rc, SOLIDSYSLOG_TLS_STREAM_ERROR_DEFAULTS_NOT_APPLIED)
334 : : );
335 : : }
336 : 157 : return ok;
337 : : }
338 : :
339 : : /* Mbed TLS names an allocation failure in its return, so say so rather than
340 : : * report the phase's own fault - which would send an integrator to their
341 : : * configuration or to the peer, and the allocator is neither. */
342 : 59 : static inline enum SolidSyslogTlsStreamErrors MbedTlsStream_InitDetail(
343 : : int rc,
344 : : enum SolidSyslogTlsStreamErrors otherwise
345 : : )
346 : : {
347 [ + + ]: 59 : return MbedTlsStream_IsAllocationFailure(rc) ? SOLIDSYSLOG_TLS_STREAM_ERROR_LIBRARY_OUT_OF_MEMORY : otherwise;
348 : : }
349 : :
350 : : /* Every allocation failure the calls this adapter makes can return. */
351 : 59 : static inline bool MbedTlsStream_IsAllocationFailure(int rc)
352 : : {
353 [ + + + + : 59 : return (rc == MBEDTLS_ERR_SSL_ALLOC_FAILED) || (rc == MBEDTLS_ERR_X509_ALLOC_FAILED) ||
+ + ]
354 : : (rc == MBEDTLS_ERR_PK_ALLOC_FAILED);
355 : : }
356 : :
357 : : /* TLS policy owned by the library - set per-ssl_config so it cannot leak
358 : : * into the integrator's other ssl_configs (per coexistence contract). The
359 : : * material the policy is enforced against is not set here: the credentials
360 : : * source installs that, so this stream holds none of it. */
361 : 152 : static inline void MbedTlsStream_ApplyTlsPolicy(struct SolidSyslogMbedTlsStream* self)
362 : : {
363 : : /* Pin the floor at TLS 1.2 rather than inheriting MBEDTLS_SSL_PRESET_DEFAULT,
364 : : * which can negotiate down to TLS 1.0/1.1 on permissive integrator builds.
365 : : * The floor is stated here so downgrade resistance does not depend on the
366 : : * preset the integrator happens to have compiled in. No ceiling is set:
367 : : * RFC 9662, which updates RFC 5425, requires TLS 1.3 to be preferred
368 : : * wherever it is implemented. */
369 : 152 : mbedtls_ssl_conf_min_tls_version(&self->SslConfig, MBEDTLS_SSL_VERSION_TLS1_2);
370 : : /* RFC 9325 s3.5: a TLS 1.2 server that does not acknowledge
371 : : * renegotiation_info gets handshake_failure. The library default completes
372 : : * that handshake and only refuses to renegotiate afterwards. */
373 : 152 : mbedtls_ssl_conf_legacy_renegotiation(&self->SslConfig, MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE);
374 : : #if defined(MBEDTLS_DHM_C) && defined(MBEDTLS_SSL_CLI_C)
375 : : /* RFC 9325 s4.5. The library default is 1024. */
376 : 152 : mbedtls_ssl_conf_dhm_min_bitlen(&self->SslConfig, MBEDTLS_STREAM_DHM_MIN_BITLEN);
377 : : #endif
378 : 152 : mbedtls_ssl_conf_rng(&self->SslConfig, mbedtls_ctr_drbg_random, self->Config.Rng);
379 : 152 : }
380 : :
381 : : /* The integrator's cipher policy, applied after the library's own so a build
382 : : * that trimmed its ciphersuites is not silently widened. One list covers both
383 : : * TLS versions here; Mbed TLS reads the array for the life of the ssl_config,
384 : : * which Close frees, so it need only outlive the connection. */
385 : 152 : static inline void MbedTlsStream_ApplyCipherPolicy(struct SolidSyslogMbedTlsStream* self)
386 : : {
387 [ + + ]: 152 : if (self->Profile.CipherSuites != NULL)
388 : : {
389 : 2 : mbedtls_ssl_conf_ciphersuites(&self->SslConfig, self->Profile.CipherSuites);
390 : : }
391 : 152 : }
392 : :
393 : : /* The integrator's X.509 floor. Left alone where none is supplied, so the
394 : : * build's own default profile stands: a legacy build cannot be asked which
395 : : * algorithms it compiled in, and pinning any of them here would turn a
396 : : * configuration problem into a failed handshake. Applied before the credentials
397 : : * install, so a backend that types its own profile still has the last word. */
398 : 152 : static inline void MbedTlsStream_ApplyCertProfilePolicy(struct SolidSyslogMbedTlsStream* self)
399 : : {
400 [ + + ]: 152 : if (self->Profile.CertProfile != NULL)
401 : : {
402 : 2 : mbedtls_ssl_conf_cert_profile(&self->SslConfig, self->Profile.CertProfile);
403 : : }
404 : 152 : }
405 : :
406 : : /* Asked once per connection, after the policy is on the ssl_config and before
407 : : * the session binds to it, so material is fetched only for a connection
408 : : * actually being made. The flag is set before the call rather than after it:
409 : : * the contract is one Release per Install call whatever that call returned,
410 : : * which is what spares every backend a rollback path of its own. */
411 : 152 : static inline bool MbedTlsStream_InstallCredentials(struct SolidSyslogMbedTlsStream* self)
412 : : {
413 : 152 : self->Installed.TrustAnchorsInstalled = false;
414 : 152 : self->Installed.Fingerprints = NULL;
415 : 152 : self->Installed.FingerprintCount = 0U;
416 : 152 : self->CredentialsInstalled = true;
417 : 152 : bool ok = self->Config.Credentials->Install(self->Config.Credentials, &self->SslConfig, &self->Installed);
418 [ + + + + ]: 152 : if (ok && !MbedTlsStream_PeerIsAuthorisable(&self->Installed))
419 : : {
420 : 5 : MbedTlsStream_ReportOpenFailure(
421 : : self,
422 : : SOLIDSYSLOG_SEVERITY_ERROR,
423 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
424 : : SOLIDSYSLOG_TLS_STREAM_ERROR_NO_PEER_AUTHORISATION
425 : : );
426 : 5 : ok = false;
427 : : }
428 [ + + ]: 152 : if (ok)
429 : : {
430 : 143 : ok = MbedTlsStream_FingerprintsAreUsable(self);
431 : : }
432 [ + + ]: 152 : if (ok)
433 : : {
434 : 139 : MbedTlsStream_ApplyPeerVerificationPolicy(self);
435 : : }
436 : 152 : return ok;
437 : : }
438 : :
439 : : /* A peer is authorised by a chain to trust anchors or by a pinned certificate
440 : : * fingerprint, and RFC 5425 4.2.1 makes the second sufficient on its own. With
441 : : * neither, there is nothing to check the peer against, so the connection stops
442 : : * rather than reaching a peer this stream cannot identify. */
443 : 148 : static inline bool MbedTlsStream_PeerIsAuthorisable(const struct SolidSyslogTlsCredentialsInstalled* installed)
444 : : {
445 [ + + + + ]: 148 : return installed->TrustAnchorsInstalled || (installed->FingerprintCount > 0U);
446 : : } /* Inspected before the handshake, so a pin that cannot match is reported as
447 : : * bad configuration rather than as a refused peer. */
448 : :
449 : 143 : static inline bool MbedTlsStream_FingerprintsAreUsable(struct SolidSyslogMbedTlsStream* self)
450 : : {
451 : 143 : bool ok = true;
452 : : enum SolidSyslogTlsFingerprintListState state =
453 : 143 : SolidSyslogTlsFingerprint_InspectList(self->Installed.Fingerprints, self->Installed.FingerprintCount);
454 [ + + ]: 143 : if (state == SOLIDSYSLOG_TLS_FINGERPRINT_LIST_MALFORMED)
455 : : {
456 : 4 : MbedTlsStream_ReportOpenFailure(
457 : : self,
458 : : SOLIDSYSLOG_SEVERITY_ERROR,
459 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
460 : : SOLIDSYSLOG_TLS_STREAM_ERROR_FINGERPRINT_MALFORMED
461 : : );
462 : 4 : ok = false;
463 : : }
464 [ + + ]: 139 : else if (state == SOLIDSYSLOG_TLS_FINGERPRINT_LIST_USES_SHA1)
465 : : {
466 : 2 : MbedTlsStream_Report(
467 : : SOLIDSYSLOG_SEVERITY_WARNING,
468 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
469 : : SOLIDSYSLOG_TLS_STREAM_ERROR_FINGERPRINT_SHA1
470 : : );
471 : : }
472 : : else
473 : : {
474 : : /* Well formed, or no pins at all. */
475 : : }
476 : 143 : return ok;
477 : : }
478 : :
479 : : /* Mbed TLS fails VERIFY_REQUIRED outright when no CA chain was installed -
480 : : * MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED, whatever a verify callback decides - so a
481 : : * peer authorised by pin alone is verified optionally and judged here instead. */
482 : 139 : static inline void MbedTlsStream_ApplyPeerVerificationPolicy(struct SolidSyslogMbedTlsStream* self)
483 : : {
484 [ + + ]: 139 : int authmode = self->Installed.TrustAnchorsInstalled ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_OPTIONAL;
485 : 139 : mbedtls_ssl_conf_authmode(&self->SslConfig, authmode);
486 : 139 : mbedtls_ssl_conf_verify(&self->SslConfig, MbedTlsStream_VerifyPeer, self);
487 : 139 : }
488 : :
489 : : /* Called for each certificate in the chain, and Mbed TLS merges what each one
490 : : * leaves in *flags into a single verdict - so a chain-trust objection above the
491 : : * leaf reaches the result unless it is cleared there too. Only the leaf is
492 : : * pinned; the certificate's own validity is never cleared. */
493 : 58 : static int MbedTlsStream_VerifyPeer(void* context, mbedtls_x509_crt* crt, int depth, uint32_t* flags)
494 : : {
495 : 58 : struct SolidSyslogMbedTlsStream* self = (struct SolidSyslogMbedTlsStream*) context;
496 : :
497 [ + + ]: 58 : if (self->Installed.FingerprintCount > 0U)
498 : : {
499 [ + + ]: 28 : if (!self->Installed.TrustAnchorsInstalled)
500 : : {
501 : 15 : *flags &= ~MbedTlsStream_ChainTrustFlags();
502 : : }
503 [ + + + + ]: 28 : if ((depth == 0) && !MbedTlsStream_LeafMatchesAPin(self, crt))
504 : : {
505 : 8 : *flags |= (uint32_t) MBEDTLS_X509_BADCERT_OTHER;
506 : : }
507 : : }
508 : :
509 : 58 : return MbedTlsStream_EnforceWhereTheLibraryWillNot(self, depth, flags);
510 : : }
511 : :
512 : : /* Under VERIFY_REQUIRED the library refuses on its own and the verdict survives
513 : : * to be read, so nothing is enforced here and the flags are left to it. Under
514 : : * OPTIONAL - which a peer authorised by pin alone forces - the library clears
515 : : * the failure and runs the handshake to completion, which would hand the
516 : : * client credential to a peer about to be refused. So refuse here instead, at
517 : : * the last certificate judged and before any of ours is sent.
518 : : *
519 : : * Refusing costs the library's verdict, which is why the flags are recorded on
520 : : * the way past: mbedtls_ssl_get_verify_result answers 0xFFFFFFFF once a verify
521 : : * callback has returned an error. */
522 : 58 : static inline int MbedTlsStream_EnforceWhereTheLibraryWillNot(
523 : : struct SolidSyslogMbedTlsStream* self,
524 : : int depth,
525 : : const uint32_t* flags
526 : : )
527 : : {
528 : 58 : int result = 0;
529 [ + + ]: 58 : if (!self->Installed.TrustAnchorsInstalled)
530 : : {
531 : 15 : self->RefusedVerdict |= *flags;
532 [ + + + + ]: 15 : if ((depth == 0) && (self->RefusedVerdict != 0U))
533 : : {
534 : 8 : result = MBEDTLS_ERR_X509_FATAL_ERROR;
535 : : }
536 : : }
537 : 58 : return result;
538 : : }
539 : :
540 : : /* The objections a missing trust anchor alone produces. Every other flag
541 : : * describes the certificate itself, which a pin does not vouch for. */
542 : 15 : static inline uint32_t MbedTlsStream_ChainTrustFlags(void)
543 : : {
544 : 15 : return (uint32_t) MBEDTLS_X509_BADCERT_NOT_TRUSTED | (uint32_t) MBEDTLS_X509_BADCERT_MISSING;
545 : : }
546 : :
547 : 20 : static inline bool MbedTlsStream_LeafMatchesAPin(struct SolidSyslogMbedTlsStream* self, mbedtls_x509_crt* leaf)
548 : : {
549 : 20 : self->PinVerdict = SolidSyslogTlsFingerprint_Authorise(
550 : : self->Installed.Fingerprints,
551 : : self->Installed.FingerprintCount,
552 : : MbedTlsStream_DigestCertificate,
553 : : leaf
554 : : );
555 : 20 : return self->PinVerdict == SOLIDSYSLOG_TLS_AUTHORISATION_MATCHED;
556 : : }
557 : :
558 : : /* A hash compiled out of Mbed TLS has no md_info, which is the Core callback's
559 : : * "algorithm cannot be computed" and refuses the peer. */
560 : 20 : static bool MbedTlsStream_DigestCertificate(
561 : : /* cppcheck-suppress constParameterCallback -- the parameter is SolidSyslogTlsDigestFunction's, declared in Core. */
562 : : void* context,
563 : : enum SolidSyslogTlsHashAlgorithm algorithm,
564 : : uint8_t* digest,
565 : : size_t* length
566 : : )
567 : : {
568 : 20 : const mbedtls_x509_crt* leaf = (const mbedtls_x509_crt*) context;
569 : 20 : const mbedtls_md_info_t* info = mbedtls_md_info_from_type(MbedTlsStream_MdTypeFor(algorithm));
570 : 20 : bool ok = info != NULL;
571 : :
572 [ + + ]: 20 : if (ok)
573 : : {
574 : 18 : ok = mbedtls_md(info, leaf->raw.p, leaf->raw.len, digest) == 0;
575 : 18 : *length = mbedtls_md_get_size(info);
576 : : }
577 : :
578 : 20 : return ok;
579 : : }
580 : :
581 : : /* An algorithm this pack does not name resolves to MBEDTLS_MD_NONE, which has
582 : : * no md_info - so a hash added to Core and not handled here refuses the peer
583 : : * rather than being digested as something else. */
584 : 20 : static inline mbedtls_md_type_t MbedTlsStream_MdTypeFor(enum SolidSyslogTlsHashAlgorithm algorithm)
585 : : {
586 : 20 : mbedtls_md_type_t type = MBEDTLS_MD_NONE;
587 [ + + ]: 20 : if (algorithm == SOLIDSYSLOG_TLS_HASH_SHA1)
588 : : {
589 : 1 : type = MBEDTLS_MD_SHA1;
590 : : }
591 [ + - ]: 19 : else if (algorithm == SOLIDSYSLOG_TLS_HASH_SHA256)
592 : : {
593 : 19 : type = MBEDTLS_MD_SHA256;
594 : : }
595 : : else
596 : : {
597 : : /* Left as MBEDTLS_MD_NONE. */
598 : : }
599 : 20 : return type;
600 : : }
601 : :
602 : : /* Answers every Install, so the integrator is always told when the credential
603 : : * window has closed - including on the paths where Open failed part way. */
604 : 324 : static inline void MbedTlsStream_ReleaseCredentials(struct SolidSyslogMbedTlsStream* self)
605 : : {
606 [ + + ]: 324 : if (self->CredentialsInstalled)
607 : : {
608 : 152 : self->CredentialsInstalled = false;
609 : 152 : self->Config.Credentials->Release(self->Config.Credentials);
610 : : }
611 : 324 : }
612 : :
613 : 139 : static inline bool MbedTlsStream_BindContextToConfig(struct SolidSyslogMbedTlsStream* self)
614 : : {
615 : 139 : int rc = mbedtls_ssl_setup(&self->SslContext, &self->SslConfig);
616 : 139 : bool ok = rc == 0;
617 [ + + ]: 139 : if (!ok)
618 : : {
619 : 10 : MbedTlsStream_ReportOpenFailure(
620 : : self,
621 : : SOLIDSYSLOG_SEVERITY_ERROR,
622 : : SOLIDSYSLOG_CAT_TLS_STREAM_INIT_FAILED,
623 : : MbedTlsStream_InitDetail(rc, SOLIDSYSLOG_TLS_STREAM_ERROR_SESSION_INIT_FAILED)
624 : : );
625 : : }
626 : 139 : return ok;
627 : : }
628 : :
629 : 129 : static inline bool MbedTlsStream_ConfigureExpectedHostname(struct SolidSyslogMbedTlsStream* self)
630 : : {
631 : 129 : bool ok = true;
632 : 129 : const char* serverName = self->Profile.ServerName;
633 [ + + ]: 129 : if (serverName == NULL)
634 : : {
635 : : /* No expected identity supplied - the handshake will accept any cert that
636 : : * chains to a trusted CA, so the peer is unverified unless a pin names
637 : : * it. Surface it as a WARNING (still connect, preserving the IP-pinned /
638 : : * closed-network case) rather than swallowing the MITM-class default
639 : : * silently. */
640 : 70 : MbedTlsStream_DeclareNoPeerName(self);
641 [ + + ]: 70 : if (self->Installed.FingerprintCount == 0U)
642 : : {
643 : 54 : MbedTlsStream_Report(
644 : : SOLIDSYSLOG_SEVERITY_WARNING,
645 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
646 : : SOLIDSYSLOG_TLS_STREAM_ERROR_SERVER_NAME_NOT_SET
647 : : );
648 : : }
649 : : }
650 [ + + ]: 59 : else if (MbedTlsStream_NameBeginsWithADot(serverName))
651 : : {
652 : : /* Not an identity. Mbed TLS would never match it, leaving a refused peer
653 : : * with no reason named; refusing here names the fault, and keeps this
654 : : * adapter in step with a library that reads it as a sub-domain pattern. */
655 : 3 : MbedTlsStream_ReportOpenFailure(
656 : : self,
657 : : SOLIDSYSLOG_SEVERITY_ERROR,
658 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
659 : : SOLIDSYSLOG_TLS_STREAM_ERROR_SERVER_NAME_NOT_APPLIED
660 : : );
661 : 3 : ok = false;
662 : : }
663 [ + + ]: 56 : else if (serverName[0] != '\0')
664 : : {
665 : : /* Copies the name, so it allocates and can fail that way. */
666 : 52 : int rc = mbedtls_ssl_set_hostname(&self->SslContext, serverName);
667 : 52 : ok = rc == 0;
668 [ + + ]: 52 : if (!ok)
669 : : {
670 : 4 : MbedTlsStream_ReportOpenFailure(
671 : : self,
672 : : SOLIDSYSLOG_SEVERITY_ERROR,
673 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
674 : : MbedTlsStream_InitDetail(rc, SOLIDSYSLOG_TLS_STREAM_ERROR_SERVER_NAME_NOT_APPLIED)
675 : : );
676 : : }
677 : : }
678 : : else
679 : : {
680 : : /* Empty string is the deliberate opt-out: the integrator has no name to
681 : : * verify against (IP-pinning / private CA) and has said so explicitly, so
682 : : * connect chain-only without a diagnostic. */
683 : 4 : MbedTlsStream_DeclareNoPeerName(self);
684 : : }
685 : 129 : return ok;
686 : : }
687 : :
688 : : /* Mbed TLS 3.6.3 and later refuse a chain-verified handshake when the client
689 : : * never called mbedtls_ssl_set_hostname, and read a NULL name as the client
690 : : * saying the omission is deliberate. Earlier releases take the same call as a
691 : : * clear of nothing. A NULL name cannot fail: the only failures are a name too
692 : : * long or an allocation for one. */
693 : 74 : static inline void MbedTlsStream_DeclareNoPeerName(struct SolidSyslogMbedTlsStream* self)
694 : : {
695 : 74 : (void) mbedtls_ssl_set_hostname(&self->SslContext, NULL);
696 : 74 : }
697 : :
698 : 59 : static inline bool MbedTlsStream_NameBeginsWithADot(const char* name)
699 : : {
700 : 59 : return name[0] == '.';
701 : : }
702 : :
703 : 122 : static inline void MbedTlsStream_InstallTransportCallbacks(struct SolidSyslogMbedTlsStream* self)
704 : : {
705 : 122 : mbedtls_ssl_set_bio(&self->SslContext, self, MbedTlsStream_BioSend, MbedTlsStream_BioRecv, NULL);
706 : 122 : }
707 : :
708 : : /* Drive mbedtls_ssl_handshake to completion under non-blocking transport.
709 : : * Each call may return WANT_READ/WANT_WRITE while waiting for the multi-RTT
710 : : * handshake to progress; we sleep briefly between attempts (avoiding a busy
711 : : * spin) until either the handshake completes, hits a hard error, or the
712 : : * bounded budget expires. Each non-success exit emits a distinct
713 : : * protocol-level error code so the integrator can tell rejection from
714 : : * timeout. */
715 : 122 : static inline bool MbedTlsStream_PerformHandshake(struct SolidSyslogMbedTlsStream* self)
716 : : {
717 : 122 : uint32_t budgetMs = MbedTlsStream_ResolveHandshakeTimeoutMs(self);
718 : 122 : uint32_t totalSleptMs = 0U;
719 : 122 : bool result = false;
720 : 122 : bool done = false;
721 : :
722 [ + + ]: 1754 : while (!done)
723 : : {
724 : 1632 : int rc = mbedtls_ssl_handshake(&self->SslContext);
725 [ + + ]: 1632 : if (rc == 0)
726 : : {
727 : 77 : result = true;
728 : 77 : done = true;
729 : : }
730 [ + + ]: 1555 : else if (!MbedTlsStream_IsRetryableHandshakeRc(rc))
731 : : {
732 : 40 : MbedTlsStream_ReportOpenFailure(
733 : : self,
734 : : SOLIDSYSLOG_SEVERITY_ERROR,
735 : : SOLIDSYSLOG_CAT_TLS_STREAM_HANDSHAKE_FAILED,
736 : : MbedTlsStream_InitDetail(rc, MbedTlsStream_RefusalDetail(self))
737 : : );
738 : 40 : done = true;
739 : : }
740 [ + + ]: 1515 : else if (MbedTlsStream_IsHandshakeBudgetExhausted(totalSleptMs, budgetMs))
741 : : {
742 : 5 : MbedTlsStream_ReportOpenFailure(
743 : : self,
744 : : SOLIDSYSLOG_SEVERITY_WARNING,
745 : : SOLIDSYSLOG_CAT_TLS_STREAM_HANDSHAKE_FAILED,
746 : : SOLIDSYSLOG_TLS_STREAM_ERROR_HANDSHAKE_TIMEOUT
747 : : );
748 : 5 : done = true;
749 : : }
750 : : else
751 : : {
752 : 1510 : uint32_t pollMs = MbedTlsStream_HandshakePollMs(totalSleptMs, budgetMs);
753 : 1510 : self->Config.Sleep(pollMs);
754 : 1510 : totalSleptMs += pollMs;
755 : : }
756 : : }
757 : 122 : return result;
758 : : }
759 : :
760 : : /* The poll interval, or what is left of the budget where that is less, so the
761 : : * sleeps requested never add up to more than the budget. How long each sleep
762 : : * really takes, and the handshake itself, can still carry the attempt past it.
763 : : * Called only while budget remains. */
764 : 1510 : static inline uint32_t MbedTlsStream_HandshakePollMs(uint32_t totalSleptMs, uint32_t budgetMs)
765 : : {
766 : 1510 : uint32_t pollMs = (uint32_t) HANDSHAKE_POLL_INTERVAL_MILLISECONDS;
767 : 1510 : uint32_t remainingMs = budgetMs - totalSleptMs;
768 [ + + ]: 1510 : if (remainingMs < pollMs)
769 : : {
770 : 1 : pollMs = remainingMs;
771 : : }
772 : 1510 : return pollMs;
773 : : }
774 : :
775 : : /* Verifying optionally completes the handshake and leaves the verdict to be
776 : : * read, so a peer authorised by pin alone is refused here rather than by Mbed
777 : : * TLS. Where anchors are installed the handshake has already failed on a bad
778 : : * certificate and the verdict is clean, so this costs nothing. */
779 : 77 : static inline bool MbedTlsStream_PeerPassedVerification(struct SolidSyslogMbedTlsStream* self)
780 : : {
781 : 77 : bool ok = !MbedTlsStream_IsVerifyFailure(mbedtls_ssl_get_verify_result(&self->SslContext));
782 [ + + ]: 77 : if (!ok)
783 : : {
784 : 2 : MbedTlsStream_ReportOpenFailure(
785 : : self,
786 : : SOLIDSYSLOG_SEVERITY_ERROR,
787 : : SOLIDSYSLOG_CAT_TLS_STREAM_HANDSHAKE_FAILED,
788 : : MbedTlsStream_RefusalDetail(self)
789 : : );
790 : : }
791 : 77 : return ok;
792 : : }
793 : :
794 : : /* The verdict outlives the failed handshake - mbedTLS records every fault it
795 : : * found on the session being negotiated - so the refusal can name the check that
796 : : * produced it rather than the handshake that carried it. */
797 : 42 : static inline enum SolidSyslogTlsStreamErrors MbedTlsStream_RefusalDetail(struct SolidSyslogMbedTlsStream* self)
798 : : {
799 : 42 : enum SolidSyslogTlsStreamErrors detail = SOLIDSYSLOG_TLS_STREAM_ERROR_HANDSHAKE_REJECTED;
800 : 42 : uint32_t verdict = self->RefusedVerdict;
801 [ + + ]: 42 : if (verdict == 0U)
802 : : {
803 : 37 : verdict = mbedtls_ssl_get_verify_result(&self->SslContext);
804 : : }
805 [ + + ]: 42 : if (self->PinVerdict == SOLIDSYSLOG_TLS_AUTHORISATION_DIGEST_UNAVAILABLE)
806 : : {
807 : 1 : detail = SOLIDSYSLOG_TLS_STREAM_ERROR_FINGERPRINT_DIGEST_UNAVAILABLE;
808 : : }
809 [ + + ]: 41 : else if (MbedTlsStream_IsVerifyFailure(verdict))
810 : : {
811 : 26 : detail = MbedTlsStream_DetailForVerifyFailure(verdict);
812 : : }
813 : : else
814 : : {
815 : : /* Rejected by the peer or the protocol; no check of ours names it. */
816 : : }
817 : 42 : return detail;
818 : : }
819 : :
820 : : /* Zero is mbedTLS for "nothing wrong" and 0xFFFFFFFF for "no result to give".
821 : : * Neither is a check the peer's certificate failed, so neither may be read as a
822 : : * set of flags - every flag reads as set in the second of them. */
823 : 118 : static inline bool MbedTlsStream_IsVerifyFailure(uint32_t verdict)
824 : : {
825 : 118 : const uint32_t verifyResultUnavailable = 0xFFFFFFFFU;
826 [ + + + + ]: 118 : return (verdict != 0U) && (verdict != verifyResultUnavailable);
827 : : }
828 : :
829 : : /* The flags accumulate, so a compound verdict resolves by precedence: an
830 : : * untrusted chain is reported ahead of anything the certificate says about
831 : : * itself, because a certificate no anchor vouches for is not made acceptable by
832 : : * the dates it carries; and the dates ahead of the name, as the contract orders
833 : : * them. */
834 : 26 : static inline enum SolidSyslogTlsStreamErrors MbedTlsStream_DetailForVerifyFailure(uint32_t verdict)
835 : : {
836 : 26 : enum SolidSyslogTlsStreamErrors detail = SOLIDSYSLOG_TLS_STREAM_ERROR_PEER_CERTIFICATE_UNTRUSTED;
837 [ + + ]: 26 : if ((verdict & (uint32_t) MBEDTLS_X509_BADCERT_OTHER) != 0U)
838 : : {
839 : 6 : detail = SOLIDSYSLOG_TLS_STREAM_ERROR_PEER_FINGERPRINT_MISMATCHED;
840 : : }
841 [ + + ]: 20 : else if (MbedTlsStream_HasUnnamedVerifyFailure(verdict))
842 : : {
843 : : /* Untrusted, which the detail already holds. */
844 : : }
845 [ + + ]: 15 : else if ((verdict & (uint32_t) MBEDTLS_X509_BADCERT_EXPIRED) != 0U)
846 : : {
847 : 8 : detail = SOLIDSYSLOG_TLS_STREAM_ERROR_PEER_CERTIFICATE_EXPIRED;
848 : : }
849 [ + + ]: 7 : else if ((verdict & (uint32_t) MBEDTLS_X509_BADCERT_FUTURE) != 0U)
850 : : {
851 : 3 : detail = SOLIDSYSLOG_TLS_STREAM_ERROR_PEER_CERTIFICATE_NOT_YET_VALID;
852 : : }
853 : : else
854 : : {
855 : : /* A named flag is set and the others are not, so this is it. */
856 : 4 : detail = SOLIDSYSLOG_TLS_STREAM_ERROR_PEER_NAME_MISMATCHED;
857 : : }
858 : 26 : return detail;
859 : : }
860 : :
861 : : /* Every flag the cascade above does not name individually. A certificate that
862 : : * fails path validation for one of them is untrusted whichever it is, and the
863 : : * integrator's next step - replace the certificate, not the network - is the
864 : : * same. */
865 : 20 : static inline bool MbedTlsStream_HasUnnamedVerifyFailure(uint32_t verdict)
866 : : {
867 : 20 : const uint32_t named = (uint32_t) MBEDTLS_X509_BADCERT_CN_MISMATCH | (uint32_t) MBEDTLS_X509_BADCERT_EXPIRED |
868 : : (uint32_t) MBEDTLS_X509_BADCERT_FUTURE | (uint32_t) MBEDTLS_X509_BADCERT_OTHER;
869 : 20 : return (verdict & ~named) != 0U;
870 : : }
871 : :
872 : 1555 : static inline bool MbedTlsStream_IsRetryableHandshakeRc(int rc)
873 : : {
874 [ + + + + ]: 1555 : return (rc == MBEDTLS_ERR_SSL_WANT_READ) || (rc == MBEDTLS_ERR_SSL_WANT_WRITE);
875 : : }
876 : :
877 : 1515 : static inline bool MbedTlsStream_IsHandshakeBudgetExhausted(uint32_t totalSleptMs, uint32_t budgetMs)
878 : : {
879 : 1515 : return totalSleptMs >= budgetMs;
880 : : }
881 : :
882 : 92 : static int MbedTlsStream_BioSend(void* ctx, const unsigned char* buf, size_t len)
883 : : {
884 : 92 : struct SolidSyslogMbedTlsStream* self = (struct SolidSyslogMbedTlsStream*) ctx;
885 [ + + ]: 92 : return SolidSyslogStream_Send(self->Config.Transport, buf, len) ? (int) len : -1;
886 : : }
887 : :
888 : : /* Translate the non-blocking transport's Read contract into mbedTLS's BIO
889 : : * recv contract:
890 : : * transport > 0 -> bytes available, return the same positive count.
891 : : * transport = 0 -> would-block. Must return MBEDTLS_ERR_SSL_WANT_READ so
892 : : * the handshake retry loop polls; returning 0 or -1 would
893 : : * abort the handshake on the first non-blocking read. */
894 : 200 : static int MbedTlsStream_BioRecv(void* ctx, unsigned char* buf, size_t len)
895 : : {
896 : 200 : struct SolidSyslogMbedTlsStream* self = (struct SolidSyslogMbedTlsStream*) ctx;
897 : 200 : SolidSyslogSsize n = SolidSyslogStream_Read(self->Config.Transport, buf, len);
898 : 200 : int result = -1;
899 [ + + ]: 200 : if (n > 0)
900 : : {
901 : 197 : result = (int) n;
902 : : }
903 [ + + ]: 3 : else if (n == 0)
904 : : {
905 : 1 : result = MBEDTLS_ERR_SSL_WANT_READ;
906 : : }
907 : : else
908 : : {
909 : : /* n < 0 - transport-level error; keep result = -1 to signal a
910 : : hard failure to mbedTLS so the handshake / read aborts. */
911 : : }
912 : 200 : return result;
913 : : }
914 : :
915 : : /* TLS-level write failure means the session state is unrecoverable - close
916 : : * so the StreamSender reconnect path runs on the next tick. Fail-fast is the
917 : : * contract every TLS stream adapter honours. */
918 : 13 : static inline bool MbedTlsStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size)
919 : : {
920 : 13 : struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base);
921 : 13 : bool ok = false;
922 [ + + ]: 13 : if (MbedTlsStream_SessionIsStillOpen(base))
923 : : {
924 : 9 : int rc = mbedtls_ssl_write(&self->SslContext, (const unsigned char*) buffer, size);
925 [ + + + + ]: 9 : ok = (rc > 0) && ((size_t) rc == size);
926 [ + + ]: 9 : if (!ok)
927 : : {
928 : 5 : MbedTlsStream_Close(base);
929 : : }
930 : : }
931 : 13 : return ok;
932 : : }
933 : :
934 : : /* mbedtls_ssl_write does not look at what the peer has sent, so a collector
935 : : * that has closed the session - a close_notify and a FIN - still has the record
936 : : * accepted, and the transport beneath may not see the FIN behind the unread
937 : : * alert. Read what is waiting first: an alert or a closed transport ends the
938 : : * session here, and Read closes it. A collector sends no application data, so
939 : : * any that arrives is discarded; a peer that keeps sending is plainly still
940 : : * there, and the record goes out once the bound is reached. */
941 : 13 : static bool MbedTlsStream_SessionIsStillOpen(struct SolidSyslogStream* base)
942 : : {
943 : : unsigned char discard[MBEDTLS_STREAM_DISCARD_BYTES];
944 : 13 : SolidSyslogSsize pending = MbedTlsStream_Read(base, discard, sizeof(discard));
945 : 13 : uint32_t reads = 1U;
946 [ + + + + ]: 28 : while ((pending > 0) && (reads < MBEDTLS_STREAM_MAX_READS_BEFORE_WRITE))
947 : : {
948 : 15 : pending = MbedTlsStream_Read(base, discard, sizeof(discard));
949 : 15 : reads++;
950 : : }
951 : 13 : return pending >= 0;
952 : : }
953 : :
954 : : /* mbedtls_ssl_read has two distinct outcomes worth keeping straight:
955 : : * 1. Steady-state read: bytes available -> positive count; nothing to read
956 : : * right now -> WANT_READ -> return 0, mirroring the transport contract.
957 : : * 2. Any other negative return (alerts, renegotiation surfacing as
958 : : * WANT_WRITE, hard transport error) is fatal under fail-fast semantics
959 : : * - close internally; the caller reopens and store-and-forward replays. */
960 : 34 : static inline SolidSyslogSsize MbedTlsStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size)
961 : : {
962 : 34 : struct SolidSyslogMbedTlsStream* self = MbedTlsStream_SelfFromBase(base);
963 : 34 : int rc = mbedtls_ssl_read(&self->SslContext, (unsigned char*) buffer, size);
964 : 34 : SolidSyslogSsize result = -1;
965 [ + + ]: 34 : if (rc > 0)
966 : : {
967 : 19 : result = (SolidSyslogSsize) rc;
968 : : }
969 [ + + ]: 15 : else if (rc == MBEDTLS_ERR_SSL_WANT_READ)
970 : : {
971 : 9 : result = 0;
972 : : }
973 : : else
974 : : {
975 : 6 : MbedTlsStream_Close(base);
976 : : }
977 : 34 : return result;
978 : : }
|