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 "SolidSyslogWolfSslSettings.h"
6 : :
7 : : #include "SolidSyslogWolfSslStream.h"
8 : :
9 : : #include <stdbool.h>
10 : : #include <stddef.h>
11 : : #include <stdint.h>
12 : : #include <string.h>
13 : : #include <wolfssl/ssl.h>
14 : : #include <wolfssl/error-ssl.h>
15 : : #include <wolfssl/wolfcrypt/error-crypt.h>
16 : : #include <wolfssl/wolfcrypt/hash.h>
17 : :
18 : : /* wolfSSL compiles its notBefore / notAfter checks out entirely under
19 : : * NO_ASN_TIME, so no certificate in the chain is date-checked and nothing says
20 : : * so at runtime. The TLS contract requires the validity period to be checked,
21 : : * which is a promise that would then silently not hold, so the build stops
22 : : * here rather than shipping it.
23 : : *
24 : : * A target with no clock cannot check dates and is a legitimate deployment.
25 : : * Define SOLIDSYSLOG_WOLFSSL_NO_VALIDITY_CHECK to say so deliberately; the
26 : : * obligation is then knowingly unmet rather than quietly missing. */
27 : : #if defined(NO_ASN_TIME) && !defined(SOLIDSYSLOG_WOLFSSL_NO_VALIDITY_CHECK)
28 : : #error \
29 : : "NO_ASN_TIME is set, so certificate validity is not checked. Build wolfSSL with ASN time, or define SOLIDSYSLOG_WOLFSSL_NO_VALIDITY_CHECK to accept that."
30 : : #endif
31 : :
32 : : #include "SolidSyslogError.h"
33 : : #include "SolidSyslogErrorCategory.h"
34 : : #include "SolidSyslogNullStream.h"
35 : : #include "SolidSyslogPrival.h"
36 : : #include "SolidSyslogStream.h"
37 : : #include "SolidSyslogStreamDefinition.h"
38 : : #include "SolidSyslogTlsCredentialsInstalled.h"
39 : : #include "SolidSyslogTlsFingerprint.h"
40 : : #include "SolidSyslogTlsStreamCategories.h"
41 : : #include "SolidSyslogTunables.h"
42 : : #include "SolidSyslogWolfSslCredentialsDefinition.h"
43 : : #include "SolidSyslogWolfSslStreamErrors.h"
44 : : #include "SolidSyslogWolfSslStreamPrivate.h"
45 : :
46 : : const struct SolidSyslogErrorSource SolidSyslogWolfSslStreamErrorSource = {
47 : : "WolfSslStream",
48 : : SolidSyslogTlsStreamErrors_Name
49 : : };
50 : :
51 : : /* The handshake poll, the read-before-write probe, and the BCP 195 section 4.5
52 : : * cryptographic floor in the units each wolfSSL setter takes. */
53 : : enum
54 : : {
55 : : HANDSHAKE_POLL_INTERVAL_MILLISECONDS = 10U,
56 : : WOLFSSL_STREAM_DISCARD_BYTES = 16U,
57 : : WOLFSSL_STREAM_MAX_READS_BEFORE_WRITE = 8U,
58 : : WOLFSSL_STREAM_MIN_RSA_KEY_BITS = 2048,
59 : : WOLFSSL_STREAM_MIN_ECC_KEY_BITS = 224,
60 : : WOLFSSL_STREAM_MIN_DH_KEY_BITS = 2048U
61 : : };
62 : :
63 : : /* The checks the verify callback can see the peer fail, as bits of
64 : : * RefusedChecks. */
65 : : enum
66 : : {
67 : : WOLFSSL_STREAM_CHECK_CHAIN_TRUST = 1U,
68 : : WOLFSSL_STREAM_CHECK_NAME = 2U,
69 : : WOLFSSL_STREAM_CHECK_NOT_AFTER = 4U,
70 : : WOLFSSL_STREAM_CHECK_NOT_BEFORE = 8U,
71 : : WOLFSSL_STREAM_CHECK_FINGERPRINT = 16U,
72 : : WOLFSSL_STREAM_CHECK_FINGERPRINT_DIGEST = 32U
73 : : };
74 : :
75 : : struct SolidSyslogAddress;
76 : :
77 : : /* What a pin is the digest of: the leaf as the peer presented it, and the
78 : : * heap hint the digest is made against. */
79 : : struct WolfSslStreamPeerCertificate
80 : : {
81 : : const unsigned char* Der;
82 : : word32 Length;
83 : : void* Heap;
84 : : };
85 : :
86 : : static uint32_t WolfSslStream_NullHandshakeTimeoutGetter(void* context);
87 : : static uint32_t WolfSslStream_NullVersion(void* context);
88 : : static void WolfSslStream_NullProfile(struct SolidSyslogWolfSslProfile* profile, void* context);
89 : : static inline bool WolfSslStream_ConfigProvidesHandshakeGetter(const struct SolidSyslogWolfSslStreamConfig* config);
90 : : static inline bool WolfSslStream_ConfigProvidesVersion(const struct SolidSyslogWolfSslStreamConfig* config);
91 : : static inline bool WolfSslStream_ConfigProvidesProfile(const struct SolidSyslogWolfSslStreamConfig* config);
92 : : static inline struct SolidSyslogWolfSslStream* WolfSslStream_SelfFromBase(struct SolidSyslogStream* base);
93 : : static inline bool WolfSslStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr);
94 : : static inline bool WolfSslStream_IsOpen(const struct SolidSyslogWolfSslStream* self);
95 : : static inline bool WolfSslStream_Establish(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr);
96 : : static inline void WolfSslStream_PullProfile(struct SolidSyslogWolfSslStream* self);
97 : : static inline bool WolfSslStream_BuildContext(struct SolidSyslogWolfSslStream* self);
98 : : static inline bool WolfSslStream_InstallCredentials(struct SolidSyslogWolfSslStream* self);
99 : : static inline bool WolfSslStream_PeerIsAuthorisable(const struct SolidSyslogTlsCredentialsInstalled* installed);
100 : : static inline bool WolfSslStream_FingerprintsAreUsable(struct SolidSyslogWolfSslStream* self);
101 : : static inline bool WolfSslStream_BuildCanEnforceBoth(const struct SolidSyslogTlsCredentialsInstalled* installed);
102 : : static inline bool WolfSslStream_CreateSession(struct SolidSyslogWolfSslStream* self);
103 : : static inline bool WolfSslStream_ApplyTlsPolicy(struct SolidSyslogWolfSslStream* self);
104 : : static inline bool WolfSslStream_ApplyCipherPolicy(struct SolidSyslogWolfSslStream* self);
105 : : static inline bool WolfSslStream_ConfigureExpectedPeerName(struct SolidSyslogWolfSslStream* self);
106 : : static inline bool WolfSslStream_ApplyExpectedPeerName(struct SolidSyslogWolfSslStream* self, const char* serverName);
107 : : static inline int WolfSslStream_DeclareExpectedPeer(struct SolidSyslogWolfSslStream* self, const char* serverName);
108 : : static inline bool WolfSslStream_IsAddressLiteral(const char* name);
109 : : static inline bool WolfSslStream_IsDigitOrDot(char c);
110 : : static inline enum SolidSyslogTlsStreamErrors WolfSslStream_InitDetail(
111 : : int rc,
112 : : enum SolidSyslogTlsStreamErrors otherwise
113 : : );
114 : : static inline void WolfSslStream_InstallTransportCallbacks(struct SolidSyslogWolfSslStream* self);
115 : : static inline void WolfSslStream_ApplyPeerVerificationPolicy(struct SolidSyslogWolfSslStream* self);
116 : : static int WolfSslStream_VerifyPeer(int preverify, WOLFSSL_X509_STORE_CTX* store);
117 : : static inline bool WolfSslStream_IsMissingSignerAPinCanWaive(
118 : : const struct SolidSyslogWolfSslStream* self,
119 : : const WOLFSSL_X509_STORE_CTX* store
120 : : );
121 : : static inline bool WolfSslStream_IsPinned(const struct SolidSyslogWolfSslStream* self);
122 : : static inline bool WolfSslStream_IsPinCheckedHere(
123 : : const struct SolidSyslogWolfSslStream* self,
124 : : const WOLFSSL_X509_STORE_CTX* store
125 : : );
126 : : static inline bool WolfSslStream_LeafMatchesAPin(
127 : : struct SolidSyslogWolfSslStream* self,
128 : : const WOLFSSL_X509_STORE_CTX* store
129 : : );
130 : : static bool WolfSslStream_DigestCertificate(
131 : : void* context,
132 : : enum SolidSyslogTlsHashAlgorithm algorithm,
133 : : uint8_t* digest,
134 : : size_t* length
135 : : );
136 : : static inline enum wc_HashType WolfSslStream_HashTypeFor(enum SolidSyslogTlsHashAlgorithm algorithm);
137 : : static inline uint32_t WolfSslStream_CheckFailedForPin(enum SolidSyslogTlsAuthorisation verdict);
138 : : static inline bool WolfSslStream_StandsWithoutASigner(
139 : : struct SolidSyslogWolfSslStream* self,
140 : : const WOLFSSL_X509_STORE_CTX* store
141 : : );
142 : : static inline uint32_t WolfSslStream_CheckFailedFor(int error);
143 : : static inline uint32_t WolfSslStream_ResolveHandshakeTimeoutMs(struct SolidSyslogWolfSslStream* self);
144 : : static inline bool WolfSslStream_PerformHandshake(struct SolidSyslogWolfSslStream* self);
145 : : static inline bool WolfSslStream_PinIsConfirmed(struct SolidSyslogWolfSslStream* self);
146 : : static inline bool WolfSslStream_IsRetryableHandshakeError(int error);
147 : : static inline bool WolfSslStream_IsHandshakeBudgetExhausted(uint32_t totalSleptMs, uint32_t budgetMs);
148 : : static inline uint32_t WolfSslStream_HandshakePollMs(uint32_t totalSleptMs, uint32_t budgetMs);
149 : : static inline enum SolidSyslogTlsStreamErrors WolfSslStream_RefusalDetail(const struct SolidSyslogWolfSslStream* self);
150 : : static inline bool WolfSslStream_Failed(const struct SolidSyslogWolfSslStream* self, uint32_t check);
151 : : static inline bool WolfSslStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size);
152 : : static bool WolfSslStream_SessionIsStillOpen(struct SolidSyslogStream* base);
153 : : static inline SolidSyslogSsize WolfSslStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size);
154 : : static inline void WolfSslStream_Close(struct SolidSyslogStream* base);
155 : : static inline void WolfSslStream_ReleaseCredentials(struct SolidSyslogWolfSslStream* self);
156 : : static uint32_t WolfSslStream_Version(struct SolidSyslogStream* base);
157 : : static int WolfSslStream_IoSend(WOLFSSL* ssl, char* buf, int sz, void* ctx);
158 : : static int WolfSslStream_IoRecv(WOLFSSL* ssl, char* buf, int sz, void* ctx);
159 : :
160 : 295 : void SolidSyslogWolfSslStream_Initialise(
161 : : struct SolidSyslogStream* base,
162 : : const struct SolidSyslogWolfSslStreamConfig* config
163 : : )
164 : : {
165 : 295 : struct SolidSyslogWolfSslStream* self = WolfSslStream_SelfFromBase(base);
166 : 295 : self->Base.Open = WolfSslStream_Open;
167 : 295 : self->Base.Send = WolfSslStream_Send;
168 : 295 : self->Base.Read = WolfSslStream_Read;
169 : 295 : self->Base.Close = WolfSslStream_Close;
170 : 295 : self->Base.Version = WolfSslStream_Version;
171 : 295 : self->Config = *config;
172 : 295 : self->Context = NULL;
173 : 295 : self->Session = NULL;
174 : 295 : self->CredentialsInstalled = false;
175 : 295 : self->OpenHealth = (struct SolidSyslogStreamOpenHealth) {0};
176 [ + + ]: 295 : if (WolfSslStream_ConfigProvidesHandshakeGetter(config) == false)
177 : : {
178 : : /* Substitute the Null Object so the bounded-handshake loop has a
179 : : * single code path regardless of whether the integrator wired
180 : : * runtime tuning. */
181 : 290 : self->Config.GetHandshakeTimeoutMs = WolfSslStream_NullHandshakeTimeoutGetter;
182 : 290 : self->Config.HandshakeTimeoutContext = NULL;
183 : : }
184 [ + + ]: 295 : if (WolfSslStream_ConfigProvidesVersion(config) == false)
185 : : {
186 : 293 : self->Config.Version = WolfSslStream_NullVersion;
187 : 293 : self->Config.VersionContext = NULL;
188 : : }
189 [ + + ]: 295 : if (WolfSslStream_ConfigProvidesProfile(config) == false)
190 : : {
191 : 10 : self->Config.Profile = WolfSslStream_NullProfile;
192 : 10 : self->Config.ProfileContext = NULL;
193 : : }
194 : 295 : }
195 : :
196 : : /* Null Object substituted in Initialise when the integrator does not install a
197 : : * getter - returns the compile-time tunable so the bounded-handshake path is a
198 : : * single code path regardless of whether the integrator wired runtime tuning. */
199 : 236 : static uint32_t WolfSslStream_NullHandshakeTimeoutGetter(void* context)
200 : : {
201 : : (void) context;
202 : 236 : return (uint32_t) SOLIDSYSLOG_TLS_HANDSHAKE_TIMEOUT_MS;
203 : : }
204 : :
205 : 295 : static inline bool WolfSslStream_ConfigProvidesHandshakeGetter(const struct SolidSyslogWolfSslStreamConfig* config)
206 : : {
207 : 295 : return config->GetHandshakeTimeoutMs != NULL;
208 : : }
209 : :
210 : : /* Null Object substituted at Initialise when the integrator installs no version
211 : : * function - reports an unchanging configuration, so the sender never reconnects
212 : : * on this stream's account. */
213 : 1 : static uint32_t WolfSslStream_NullVersion(void* context)
214 : : {
215 : : (void) context;
216 : 1 : return 0U;
217 : : }
218 : :
219 : 295 : static inline bool WolfSslStream_ConfigProvidesVersion(const struct SolidSyslogWolfSslStreamConfig* config)
220 : : {
221 : 295 : return config->Version != NULL;
222 : : }
223 : :
224 : : /* Null Object substituted at Initialise when the integrator supplies no profile -
225 : : * leaves every field at the library default. */
226 : 1 : static void WolfSslStream_NullProfile(struct SolidSyslogWolfSslProfile* profile, void* context)
227 : : {
228 : : (void) context;
229 : : (void) profile;
230 : 1 : }
231 : :
232 : 295 : static inline bool WolfSslStream_ConfigProvidesProfile(const struct SolidSyslogWolfSslStreamConfig* config)
233 : : {
234 : 295 : return config->Profile != NULL;
235 : : }
236 : :
237 : 1439 : static inline struct SolidSyslogWolfSslStream* WolfSslStream_SelfFromBase(struct SolidSyslogStream* base)
238 : : {
239 : 1439 : return (struct SolidSyslogWolfSslStream*) base;
240 : : }
241 : :
242 : 295 : void SolidSyslogWolfSslStream_Cleanup(struct SolidSyslogStream* base)
243 : : {
244 : : /* An integrator who destroys a still-Open stream must not leak the
245 : : * underlying TLS state. */
246 : 295 : WolfSslStream_Close(base);
247 : : /* Overwrite the abstract base with the shared NullStream vtable so
248 : : * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */
249 : 295 : *base = *SolidSyslogNullStream_Get();
250 : 295 : }
251 : :
252 : : /* Idempotent: everything freed here is set NULL as it goes, so a second Close
253 : : * finds nothing to free, and the transport's own Close is idempotent on every
254 : : * Stream impl. close_notify is sent once and not waited for - the collector's
255 : : * reply would need another service pass to arrive, and nothing is read on a
256 : : * session being torn down. */
257 : 499 : static inline void WolfSslStream_Close(struct SolidSyslogStream* base)
258 : : {
259 : 499 : struct SolidSyslogWolfSslStream* self = WolfSslStream_SelfFromBase(base);
260 [ + + ]: 499 : if (self->Session != NULL)
261 : : {
262 : 261 : (void) wolfSSL_shutdown(self->Session);
263 : 261 : wolfSSL_free(self->Session);
264 : 261 : self->Session = NULL;
265 : : }
266 : : /* The context holds the credentials' material until it is freed, so the
267 : : credentials are told the window has closed only after wolfSSL has let go
268 : : of it. */
269 [ + + ]: 499 : if (self->Context != NULL)
270 : : {
271 : 284 : wolfSSL_CTX_free(self->Context);
272 : 284 : self->Context = NULL;
273 : : }
274 : 499 : WolfSslStream_ReleaseCredentials(self);
275 : 499 : SolidSyslogStream_Close(self->Config.Transport);
276 : 499 : }
277 : :
278 : : /* Answers every Install, so the integrator is always told when the credential
279 : : * window has closed - including on the paths where Open failed part way. */
280 : 499 : static inline void WolfSslStream_ReleaseCredentials(struct SolidSyslogWolfSslStream* self)
281 : : {
282 [ + + ]: 499 : if (self->CredentialsInstalled)
283 : : {
284 : 284 : self->CredentialsInstalled = false;
285 : 284 : self->Config.Credentials->Release(self->Config.Credentials);
286 : : }
287 : 499 : }
288 : :
289 : 3 : static uint32_t WolfSslStream_Version(struct SolidSyslogStream* base)
290 : : {
291 : 3 : struct SolidSyslogWolfSslStream* self = WolfSslStream_SelfFromBase(base);
292 : 3 : return self->Config.Version(self->Config.VersionContext);
293 : : }
294 : :
295 : 291 : static inline bool WolfSslStream_Open(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr)
296 : : {
297 : 291 : struct SolidSyslogWolfSslStream* self = WolfSslStream_SelfFromBase(base);
298 : 291 : bool ok = false;
299 [ + + ]: 291 : if (WolfSslStream_IsOpen(self))
300 : : {
301 : : /* Refused by contract, not reconnected: what is held stays as it is. */
302 : 2 : WolfSslStream_Report(
303 : : SOLIDSYSLOG_BAD_ARGUMENT_SEVERITY,
304 : : SOLIDSYSLOG_CAT_BAD_ARGUMENT,
305 : : SOLIDSYSLOG_TLS_STREAM_ERROR_ALREADY_OPEN
306 : : );
307 : : }
308 : : else
309 : : {
310 : 289 : ok = WolfSslStream_Establish(base, addr);
311 [ + + ]: 289 : if (ok)
312 : : {
313 : 132 : SolidSyslogStreamOpenHealth_Opened(&self->OpenHealth);
314 : : }
315 : : }
316 : 291 : return ok;
317 : : }
318 : :
319 : : /* Every Open that succeeds installs the credentials and every Close releases
320 : : * them, so holding them is the one mark of a session still open. */
321 : 291 : static inline bool WolfSslStream_IsOpen(const struct SolidSyslogWolfSslStream* self)
322 : : {
323 : 291 : return self->CredentialsInstalled;
324 : : }
325 : :
326 : 289 : static inline bool WolfSslStream_Establish(struct SolidSyslogStream* base, const struct SolidSyslogAddress* addr)
327 : : {
328 : 289 : struct SolidSyslogWolfSslStream* self = WolfSslStream_SelfFromBase(base);
329 : 289 : WolfSslStream_PullProfile(self);
330 : 289 : self->RefusedChecks = 0U;
331 : 289 : self->PeerPinned = false;
332 [ + + + + ]: 861 : bool ok = SolidSyslogStream_Open(self->Config.Transport, addr) && WolfSslStream_BuildContext(self) &&
333 [ + + + + ]: 813 : WolfSslStream_InstallCredentials(self) && WolfSslStream_CreateSession(self) &&
334 [ + + + + : 1087 : WolfSslStream_ApplyTlsPolicy(self) && WolfSslStream_ApplyCipherPolicy(self) &&
+ + ]
335 : 249 : WolfSslStream_ConfigureExpectedPeerName(self);
336 [ + + ]: 289 : if (ok)
337 : : {
338 : 241 : WolfSslStream_InstallTransportCallbacks(self);
339 : 241 : WolfSslStream_ApplyPeerVerificationPolicy(self);
340 : 241 : ok = WolfSslStream_PerformHandshake(self);
341 : : }
342 [ + + ]: 289 : if (!ok)
343 : : {
344 : 157 : WolfSslStream_Close(base);
345 : : }
346 : 289 : return ok;
347 : : }
348 : :
349 : : /* One snapshot per connection: every later step reads the same answer, however
350 : : * the integrator's own state moves while the handshake is in progress. */
351 : 289 : static inline void WolfSslStream_PullProfile(struct SolidSyslogWolfSslStream* self)
352 : : {
353 : 289 : self->Profile = (struct SolidSyslogWolfSslProfile) {0};
354 : 289 : self->Config.Profile(&self->Profile, self->Config.ProfileContext);
355 : 289 : }
356 : :
357 : : /* A context per connection, on the integrator's heap hint, so nothing of one
358 : : * connection's material survives into the next. wolfSSL_CTX_new_ex takes the
359 : : * method over whatever it returns, so a NULL here leaks nothing. */
360 : 288 : static inline bool WolfSslStream_BuildContext(struct SolidSyslogWolfSslStream* self)
361 : : {
362 : 288 : self->Context = wolfSSL_CTX_new_ex(wolfTLS_client_method_ex(self->Config.Heap), self->Config.Heap);
363 : 288 : bool ok = self->Context != NULL;
364 [ + + ]: 288 : if (!ok)
365 : : {
366 : 4 : WolfSslStream_ReportOpenFailure(
367 : : self,
368 : : SOLIDSYSLOG_SEVERITY_ERROR,
369 : : SOLIDSYSLOG_CAT_TLS_STREAM_INIT_FAILED,
370 : : SOLIDSYSLOG_TLS_STREAM_ERROR_CONTEXT_INIT_FAILED
371 : : );
372 : : }
373 : 288 : return ok;
374 : : }
375 : :
376 : : /* Asked once per connection, onto the context and before the session is created
377 : : * from it, so what is installed is what the session inherits. The flag is set
378 : : * before the call rather than after it: the contract is one Release per Install
379 : : * call whatever that call returned, which is what spares every backend a
380 : : * rollback path of its own. */
381 : 284 : static inline bool WolfSslStream_InstallCredentials(struct SolidSyslogWolfSslStream* self)
382 : : {
383 : 284 : self->Installed.TrustAnchorsInstalled = false;
384 : 284 : self->Installed.Fingerprints = NULL;
385 : 284 : self->Installed.FingerprintCount = 0U;
386 : 284 : self->CredentialsInstalled = true;
387 : 284 : bool ok = self->Config.Credentials->Install(self->Config.Credentials, self->Context, &self->Installed);
388 [ + + + + ]: 284 : if (ok && !WolfSslStream_PeerIsAuthorisable(&self->Installed))
389 : : {
390 : 6 : WolfSslStream_ReportOpenFailure(
391 : : self,
392 : : SOLIDSYSLOG_SEVERITY_ERROR,
393 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
394 : : SOLIDSYSLOG_TLS_STREAM_ERROR_NO_PEER_AUTHORISATION
395 : : );
396 : 6 : ok = false;
397 : : }
398 [ + + ]: 284 : if (ok)
399 : : {
400 : 275 : ok = WolfSslStream_FingerprintsAreUsable(self);
401 : : }
402 [ + + + + ]: 284 : if (ok && !WolfSslStream_BuildCanEnforceBoth(&self->Installed))
403 : : {
404 : 4 : WolfSslStream_ReportOpenFailure(
405 : : self,
406 : : SOLIDSYSLOG_SEVERITY_ERROR,
407 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
408 : : SOLIDSYSLOG_TLS_STREAM_ERROR_PINS_WITH_ANCHORS_UNSUPPORTED
409 : : );
410 : 4 : ok = false;
411 : : }
412 : 284 : return ok;
413 : : }
414 : :
415 : : /* A peer is authorised by a chain to trust anchors or by a pinned certificate
416 : : * fingerprint, and RFC 5425 section 4.2.1 makes the second sufficient on its
417 : : * own. With neither, there is nothing to check the peer against, so the
418 : : * connection stops rather than reaching a peer this stream cannot identify. */
419 : 281 : static inline bool WolfSslStream_PeerIsAuthorisable(const struct SolidSyslogTlsCredentialsInstalled* installed)
420 : : {
421 [ + + + + ]: 281 : return installed->TrustAnchorsInstalled || (installed->FingerprintCount > 0U);
422 : : }
423 : :
424 : : /* Inspected before the handshake, so a pin that cannot match is reported as
425 : : * bad configuration rather than as a refused peer. */
426 : 275 : static inline bool WolfSslStream_FingerprintsAreUsable(struct SolidSyslogWolfSslStream* self)
427 : : {
428 : 275 : bool ok = true;
429 : : enum SolidSyslogTlsFingerprintListState state =
430 : 275 : SolidSyslogTlsFingerprint_InspectList(self->Installed.Fingerprints, self->Installed.FingerprintCount);
431 [ + + ]: 275 : if (state == SOLIDSYSLOG_TLS_FINGERPRINT_LIST_MALFORMED)
432 : : {
433 : 3 : WolfSslStream_ReportOpenFailure(
434 : : self,
435 : : SOLIDSYSLOG_SEVERITY_ERROR,
436 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
437 : : SOLIDSYSLOG_TLS_STREAM_ERROR_FINGERPRINT_MALFORMED
438 : : );
439 : 3 : ok = false;
440 : : }
441 [ + + ]: 272 : else if (state == SOLIDSYSLOG_TLS_FINGERPRINT_LIST_USES_SHA1)
442 : : {
443 : 5 : WolfSslStream_Report(
444 : : SOLIDSYSLOG_SEVERITY_WARNING,
445 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
446 : : SOLIDSYSLOG_TLS_STREAM_ERROR_FINGERPRINT_SHA1
447 : : );
448 : : }
449 : : else
450 : : {
451 : : /* Well formed, or no pins at all. */
452 : : }
453 : 275 : return ok;
454 : : }
455 : :
456 : : /* Where anchors and pins are both installed the peer must satisfy both, and a
457 : : * build that cannot check a pin on a chain that verifies would satisfy only
458 : : * the chain - so the connection stops rather than enforce half of it. */
459 : 272 : static inline bool WolfSslStream_BuildCanEnforceBoth(const struct SolidSyslogTlsCredentialsInstalled* installed)
460 : : {
461 [ + + + + ]: 272 : bool both = installed->TrustAnchorsInstalled && (installed->FingerprintCount > 0U);
462 [ + + + + ]: 272 : return !both || (SolidSyslogWolfSslStream_AnchoredPinCheck() != SOLIDSYSLOG_WOLFSSL_PIN_BESIDE_ANCHORS_REFUSED);
463 : : }
464 : :
465 : 268 : static inline bool WolfSslStream_CreateSession(struct SolidSyslogWolfSslStream* self)
466 : : {
467 : 268 : self->Session = wolfSSL_new(self->Context);
468 : 268 : bool ok = self->Session != NULL;
469 [ + + ]: 268 : if (!ok)
470 : : {
471 : 7 : WolfSslStream_ReportOpenFailure(
472 : : self,
473 : : SOLIDSYSLOG_SEVERITY_ERROR,
474 : : SOLIDSYSLOG_CAT_TLS_STREAM_INIT_FAILED,
475 : : SOLIDSYSLOG_TLS_STREAM_ERROR_SESSION_INIT_FAILED
476 : : );
477 : : }
478 : 268 : return ok;
479 : : }
480 : :
481 : : /* TLS policy owned by the library, set on the session so it cannot leak into
482 : : * anything the integrator builds from a context of their own. The floor is
483 : : * TLS 1.2, stated here so downgrade resistance does not depend on how the
484 : : * library was built; no ceiling is set, because RFC 9662, which updates RFC
485 : : * 5425, requires TLS 1.3 to be preferred wherever it is implemented. The key
486 : : * sizes are BCP 195 section 4.5. */
487 : 261 : static inline bool WolfSslStream_ApplyTlsPolicy(struct SolidSyslogWolfSslStream* self)
488 : : {
489 [ + + ]: 519 : bool ok = (wolfSSL_SetMinVersion(self->Session, WOLFSSL_TLSV1_2) == WOLFSSL_SUCCESS) &&
490 [ + + ]: 515 : (wolfSSL_SetMinRsaKey_Sz(self->Session, (short) WOLFSSL_STREAM_MIN_RSA_KEY_BITS) == WOLFSSL_SUCCESS) &&
491 [ + + + + ]: 776 : (wolfSSL_SetMinEccKey_Sz(self->Session, (short) WOLFSSL_STREAM_MIN_ECC_KEY_BITS) == WOLFSSL_SUCCESS) &&
492 : 256 : (wolfSSL_SetMinDhKey_Sz(self->Session, (word16) WOLFSSL_STREAM_MIN_DH_KEY_BITS) == WOLFSSL_SUCCESS);
493 [ + + ]: 261 : if (!ok)
494 : : {
495 : 6 : WolfSslStream_ReportOpenFailure(
496 : : self,
497 : : SOLIDSYSLOG_SEVERITY_ERROR,
498 : : SOLIDSYSLOG_CAT_TLS_STREAM_INIT_FAILED,
499 : : SOLIDSYSLOG_TLS_STREAM_ERROR_DEFAULTS_NOT_APPLIED
500 : : );
501 : : }
502 : 261 : return ok;
503 : : }
504 : :
505 : : /* wolfSSL rejects a list in which it knows no name, which is reported as the
506 : : * configuration fault it is, before any handshake. */
507 : 255 : static inline bool WolfSslStream_ApplyCipherPolicy(struct SolidSyslogWolfSslStream* self)
508 : : {
509 : 255 : bool ok = true;
510 [ + + ]: 255 : if (self->Profile.CipherList != NULL)
511 : : {
512 : 16 : ok = wolfSSL_set_cipher_list(self->Session, self->Profile.CipherList) == WOLFSSL_SUCCESS;
513 [ + + ]: 16 : if (!ok)
514 : : {
515 : 6 : WolfSslStream_ReportOpenFailure(
516 : : self,
517 : : SOLIDSYSLOG_SEVERITY_ERROR,
518 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
519 : : SOLIDSYSLOG_TLS_STREAM_ERROR_CIPHER_POLICY_REJECTED
520 : : );
521 : : }
522 : : }
523 : 255 : return ok;
524 : : }
525 : :
526 : : /* The declared name is both what the certificate is checked against and what is
527 : : * sent as the server name indication. */
528 : 249 : static inline bool WolfSslStream_ConfigureExpectedPeerName(struct SolidSyslogWolfSslStream* self)
529 : : {
530 : 249 : bool ok = true;
531 : 249 : const char* serverName = self->Profile.ServerName;
532 [ + + ]: 249 : if (serverName == NULL)
533 : : {
534 : : /* No expected identity supplied - the handshake will accept any cert that
535 : : * chains to a trusted CA, so the peer is unverified unless a pin names
536 : : * it, which is a stronger declaration than a name. Surface it as a
537 : : * WARNING (still connect, preserving the closed-network case) rather than
538 : : * swallowing the MITM-class default silently. */
539 [ + + ]: 10 : if (!WolfSslStream_IsPinned(self))
540 : : {
541 : 6 : WolfSslStream_Report(
542 : : SOLIDSYSLOG_SEVERITY_WARNING,
543 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
544 : : SOLIDSYSLOG_TLS_STREAM_ERROR_SERVER_NAME_NOT_SET
545 : : );
546 : : }
547 : : }
548 [ + + ]: 239 : else if (serverName[0] != '\0')
549 : : {
550 : 234 : ok = WolfSslStream_ApplyExpectedPeerName(self, serverName);
551 : : }
552 : : else
553 : : {
554 : : /* Empty string is the deliberate opt-out: the integrator has no name to
555 : : * verify against (private CA) and has said so explicitly, so connect
556 : : * chain-only without a diagnostic. */
557 : : }
558 : 249 : return ok;
559 : : }
560 : :
561 : : /* The declared name is both what the certificate is checked against and what is
562 : : * sent as the server name indication. Both copy the name, so both can fail on
563 : : * allocation; only the indication says so in its return. */
564 : 234 : static inline bool WolfSslStream_ApplyExpectedPeerName(struct SolidSyslogWolfSslStream* self, const char* serverName)
565 : : {
566 : 234 : bool ok = false;
567 [ + + ]: 234 : if (WolfSslStream_DeclareExpectedPeer(self, serverName) != WOLFSSL_SUCCESS)
568 : : {
569 : 4 : WolfSslStream_ReportOpenFailure(
570 : : self,
571 : : SOLIDSYSLOG_SEVERITY_ERROR,
572 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
573 : : SOLIDSYSLOG_TLS_STREAM_ERROR_SERVER_NAME_NOT_APPLIED
574 : : );
575 : : }
576 : : else
577 : : {
578 : 230 : int rc = wolfSSL_UseSNI(self->Session, WOLFSSL_SNI_HOST_NAME, serverName, (word16) strlen(serverName));
579 : 230 : ok = rc == WOLFSSL_SUCCESS;
580 [ + + ]: 230 : if (!ok)
581 : : {
582 : 4 : WolfSslStream_ReportOpenFailure(
583 : : self,
584 : : SOLIDSYSLOG_SEVERITY_ERROR,
585 : : SOLIDSYSLOG_CAT_BAD_CONFIG,
586 : : WolfSslStream_InitDetail(rc, SOLIDSYSLOG_TLS_STREAM_ERROR_SERVER_NAME_NOT_APPLIED)
587 : : );
588 : : }
589 : : }
590 : 234 : return ok;
591 : : }
592 : :
593 : : /* wolfSSL keeps a name and an address apart: a name is matched against the
594 : : * dNSName entries or the Common Name, and validated as a hostname when it is
595 : : * declared; an address only ever against the iPAddress entries. The contract
596 : : * wants the same split, so which call takes the declaration is decided here
597 : : * rather than left to the library's hostname rules to refuse. */
598 : 234 : static inline int WolfSslStream_DeclareExpectedPeer(struct SolidSyslogWolfSslStream* self, const char* serverName)
599 : : {
600 : 244 : return WolfSslStream_IsAddressLiteral(serverName) ? wolfSSL_check_ip_address(self->Session, serverName)
601 [ + + ]: 244 : : wolfSSL_check_domain_name(self->Session, serverName);
602 : : }
603 : :
604 : : /* A colon is only ever in an IPv6 literal, and a name made of nothing but
605 : : * digits and dots is an IPv4 one - no hostname has an all-numeric last label.
606 : : * What the literal is worth is the library's to judge against the certificate. */
607 : 234 : static inline bool WolfSslStream_IsAddressLiteral(const char* name)
608 : : {
609 : 234 : bool onlyDigitsAndDots = true;
610 : 234 : bool hasColon = false;
611 [ + + ]: 4382 : for (size_t index = 0U; name[index] != '\0'; index++)
612 : : {
613 [ + + ]: 4148 : if (name[index] == ':')
614 : : {
615 : 2 : hasColon = true;
616 : : }
617 [ + + ]: 4148 : if (!WolfSslStream_IsDigitOrDot(name[index]))
618 : : {
619 : 3615 : onlyDigitsAndDots = false;
620 : : }
621 : : }
622 [ + + + + ]: 234 : return hasColon || onlyDigitsAndDots;
623 : : }
624 : :
625 : 4148 : static inline bool WolfSslStream_IsDigitOrDot(char c)
626 : : {
627 [ + + + + : 4148 : return ((c >= '0') && (c <= '9')) || (c == '.');
+ + ]
628 : : }
629 : :
630 : : /* wolfSSL names an allocation failure in its return, so say so rather than
631 : : * report the phase's own fault - which would send an integrator to their
632 : : * configuration or to the peer, and the allocator is neither. */
633 : 88 : static inline enum SolidSyslogTlsStreamErrors WolfSslStream_InitDetail(
634 : : int rc,
635 : : enum SolidSyslogTlsStreamErrors otherwise
636 : : )
637 : : {
638 [ + + ]: 88 : return (rc == MEMORY_E) ? SOLIDSYSLOG_TLS_STREAM_ERROR_LIBRARY_OUT_OF_MEMORY : otherwise;
639 : : }
640 : :
641 : 241 : static inline void WolfSslStream_InstallTransportCallbacks(struct SolidSyslogWolfSslStream* self)
642 : : {
643 : 241 : wolfSSL_SSLSetIORecv(self->Session, WolfSslStream_IoRecv);
644 : 241 : wolfSSL_SSLSetIOSend(self->Session, WolfSslStream_IoSend);
645 : 241 : wolfSSL_SetIOReadCtx(self->Session, self);
646 : 241 : wolfSSL_SetIOWriteCtx(self->Session, self);
647 : 241 : }
648 : :
649 : : /* The peer must present a certificate and it must verify; the callback records
650 : : * what the library found wrong, so the refusal can name the check. */
651 : 241 : static inline void WolfSslStream_ApplyPeerVerificationPolicy(struct SolidSyslogWolfSslStream* self)
652 : : {
653 : 241 : wolfSSL_set_verify(
654 : : self->Session,
655 : : WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT,
656 : : WolfSslStream_VerifyPeer
657 : : );
658 : 241 : wolfSSL_SetCertCbCtx(self->Session, self);
659 : 241 : }
660 : :
661 : : /* Called for each check a certificate in the chain fails - and, on a build
662 : : * with WOLFSSL_ALWAYS_VERIFY_CB, for the leaf once it passes. The library's
663 : : * verdict stands but for one objection: with pins and no anchors there is no
664 : : * signer for any chain to reach, so a missing one is waived for a peer a pin
665 : : * names. A pin that names nothing refuses the peer whatever the chain says.
666 : : * Every check is recorded as it is decided, because the handshake's return
667 : : * carries one code and not necessarily the one the contract puts first. */
668 : 151 : static int WolfSslStream_VerifyPeer(int preverify, WOLFSSL_X509_STORE_CTX* store)
669 : : {
670 : 151 : struct SolidSyslogWolfSslStream* self = (struct SolidSyslogWolfSslStream*) store->userCtx;
671 : 151 : int verdict = preverify;
672 [ + + ]: 151 : if (WolfSslStream_IsMissingSignerAPinCanWaive(self, store))
673 : : {
674 : 46 : verdict = 0;
675 [ + + + + ]: 46 : if (WolfSslStream_LeafMatchesAPin(self, store) && WolfSslStream_StandsWithoutASigner(self, store))
676 : : {
677 : 31 : verdict = 1;
678 : : }
679 : : }
680 : : else
681 : : {
682 : 105 : self->RefusedChecks |= WolfSslStream_CheckFailedFor(store->error);
683 [ + + + + ]: 105 : if (WolfSslStream_IsPinCheckedHere(self, store) && !WolfSslStream_LeafMatchesAPin(self, store))
684 : : {
685 : 6 : verdict = 0;
686 : : }
687 : : }
688 : 151 : return verdict;
689 : : }
690 : :
691 : 151 : static inline bool WolfSslStream_IsMissingSignerAPinCanWaive(
692 : : const struct SolidSyslogWolfSslStream* self,
693 : : const WOLFSSL_X509_STORE_CTX* store
694 : : )
695 : : {
696 [ + + + + : 151 : return WolfSslStream_IsPinned(self) && !self->Installed.TrustAnchorsInstalled && (store->error == ASN_NO_SIGNER_E);
+ + ]
697 : : }
698 : :
699 : 572 : static inline bool WolfSslStream_IsPinned(const struct SolidSyslogWolfSslStream* self)
700 : : {
701 : 572 : return self->Installed.FingerprintCount > 0U;
702 : : }
703 : :
704 : : /* Pins alone are checked on every call. Beside anchors, a pin is checked
705 : : * wherever the library reports a fault, so a peer the pins do not name is
706 : : * named for that first; and on a leaf that verified only where this build's
707 : : * body checks it here - a build that cannot call back for a verified leaf
708 : : * checks it once the handshake is done instead. */
709 : 105 : static inline bool WolfSslStream_IsPinCheckedHere(
710 : : const struct SolidSyslogWolfSslStream* self,
711 : : const WOLFSSL_X509_STORE_CTX* store
712 : : )
713 : : {
714 [ + + ]: 140 : return WolfSslStream_IsPinned(self) &&
715 [ + + + + : 45 : (!self->Installed.TrustAnchorsInstalled || (store->error != 0) ||
+ + ]
716 : 10 : (SolidSyslogWolfSslStream_AnchoredPinCheck() == SOLIDSYSLOG_WOLFSSL_PIN_BESIDE_ANCHORS_IN_CALLBACK));
717 : : }
718 : :
719 : : /* A pin is the digest of the leaf. wolfSSL hands the callback the whole chain
720 : : * the peer presented, leaf first, whichever certificate it is calling back
721 : : * for. */
722 : 78 : static inline bool WolfSslStream_LeafMatchesAPin(
723 : : struct SolidSyslogWolfSslStream* self,
724 : : const WOLFSSL_X509_STORE_CTX* store
725 : : )
726 : : {
727 : 78 : return SolidSyslogWolfSslStream_AuthoriseLeaf(self, store->certs[0].buffer, store->certs[0].length);
728 : : }
729 : :
730 : 87 : bool SolidSyslogWolfSslStream_AuthoriseLeaf(
731 : : struct SolidSyslogWolfSslStream* self,
732 : : const unsigned char* der,
733 : : word32 length
734 : : )
735 : : {
736 : 87 : struct WolfSslStreamPeerCertificate leaf = {der, length, self->Config.Heap};
737 : 87 : enum SolidSyslogTlsAuthorisation verdict = SolidSyslogTlsFingerprint_Authorise(
738 : : self->Installed.Fingerprints,
739 : : self->Installed.FingerprintCount,
740 : : WolfSslStream_DigestCertificate,
741 : : &leaf
742 : : );
743 : 87 : self->RefusedChecks |= WolfSslStream_CheckFailedForPin(verdict);
744 : 87 : self->PeerPinned = verdict == SOLIDSYSLOG_TLS_AUTHORISATION_MATCHED;
745 : 87 : return self->PeerPinned;
746 : : }
747 : :
748 : 92 : static bool WolfSslStream_DigestCertificate(
749 : : /* cppcheck-suppress constParameterCallback -- the parameter is SolidSyslogTlsDigestFunction's, declared in Core. */
750 : : void* context,
751 : : enum SolidSyslogTlsHashAlgorithm algorithm,
752 : : uint8_t* digest,
753 : : size_t* length
754 : : )
755 : : {
756 : 92 : const struct WolfSslStreamPeerCertificate* certificate = (const struct WolfSslStreamPeerCertificate*) context;
757 : 92 : enum wc_HashType type = WolfSslStream_HashTypeFor(algorithm);
758 : 92 : int size = wc_HashGetDigestSize(type);
759 [ + + + + : 181 : bool ok = (certificate->Der != NULL) && (size > 0) &&
+ + ]
760 : 89 : (wc_Hash_ex(
761 : : type,
762 : 89 : certificate->Der,
763 : 89 : certificate->Length,
764 : : digest,
765 : : (word32) size,
766 : 89 : certificate->Heap,
767 : : INVALID_DEVID
768 : : ) == 0);
769 [ + + ]: 92 : if (ok)
770 : : {
771 : 88 : *length = (size_t) size;
772 : : }
773 : 92 : return ok;
774 : : }
775 : :
776 : 92 : static inline enum wc_HashType WolfSslStream_HashTypeFor(enum SolidSyslogTlsHashAlgorithm algorithm)
777 : : {
778 : 92 : enum wc_HashType type = WC_HASH_TYPE_SHA256;
779 [ + + ]: 92 : if (algorithm == SOLIDSYSLOG_TLS_HASH_SHA1)
780 : : {
781 : 7 : type = WC_HASH_TYPE_SHA;
782 : : }
783 : 92 : return type;
784 : : }
785 : :
786 : : /* Only a match authorises. A pin that will not parse was refused at Open, so a
787 : : * verdict that is neither a match nor an uncomputable digest is a peer the pins
788 : : * do not name. */
789 : 87 : static inline uint32_t WolfSslStream_CheckFailedForPin(enum SolidSyslogTlsAuthorisation verdict)
790 : : {
791 : 87 : uint32_t check = WOLFSSL_STREAM_CHECK_FINGERPRINT;
792 [ + + ]: 87 : if (verdict == SOLIDSYSLOG_TLS_AUTHORISATION_MATCHED)
793 : : {
794 : 69 : check = 0U;
795 : : }
796 [ + + ]: 18 : else if (verdict == SOLIDSYSLOG_TLS_AUTHORISATION_DIGEST_UNAVAILABLE)
797 : : {
798 : 4 : check = WOLFSSL_STREAM_CHECK_FINGERPRINT_DIGEST;
799 : : }
800 : : else
801 : : {
802 : : /* No pin names the peer, which the check already holds. */
803 : : }
804 : 87 : return check;
805 : : }
806 : :
807 : : /* With no anchor wolfSSL stops at the missing signer, and what it found on the
808 : : * way - the certificate's dates, an unknown critical extension - is never
809 : : * reported. So the stream checks them itself, parsing the certificate as a
810 : : * trust anchor is loaded: its own dates and extensions, and nothing above it,
811 : : * which is what a certificate authorised by a pin is. */
812 : 39 : static inline bool WolfSslStream_StandsWithoutASigner(
813 : : struct SolidSyslogWolfSslStream* self,
814 : : const WOLFSSL_X509_STORE_CTX* store
815 : : )
816 : : {
817 : 39 : const WOLFSSL_BUFFER_INFO* certificate = &store->certs[store->error_depth];
818 : 39 : wc_InitDecodedCert(&self->DecodedCertificate, certificate->buffer, certificate->length, self->Config.Heap);
819 : 39 : int error = wc_ParseCert(&self->DecodedCertificate, CA_TYPE, VERIFY, NULL);
820 : 39 : wc_FreeDecodedCert(&self->DecodedCertificate);
821 : 39 : self->RefusedChecks |= WolfSslStream_CheckFailedFor(error);
822 : 39 : return error == 0;
823 : : }
824 : :
825 : : /* Every code the stream does not name individually is a certificate that
826 : : * fails path validation for a reason the integrator's next step - replace the
827 : : * certificate, not the network - does not depend on, so it reads as untrusted. */
828 : 144 : static inline uint32_t WolfSslStream_CheckFailedFor(int error)
829 : : {
830 : 144 : uint32_t check = 0U;
831 [ + + ]: 144 : if (error == 0)
832 : : {
833 : : /* The certificate passed; nothing to record. */
834 : : }
835 [ + + + + ]: 70 : else if ((error == DOMAIN_NAME_MISMATCH) || (error == IPADDR_MISMATCH))
836 : : {
837 : 18 : check = WOLFSSL_STREAM_CHECK_NAME;
838 : : }
839 [ + + ]: 52 : else if (error == ASN_AFTER_DATE_E)
840 : : {
841 : 16 : check = WOLFSSL_STREAM_CHECK_NOT_AFTER;
842 : : }
843 [ + + ]: 36 : else if (error == ASN_BEFORE_DATE_E)
844 : : {
845 : 9 : check = WOLFSSL_STREAM_CHECK_NOT_BEFORE;
846 : : }
847 : : else
848 : : {
849 : 27 : check = WOLFSSL_STREAM_CHECK_CHAIN_TRUST;
850 : : }
851 : 144 : return check;
852 : : }
853 : :
854 : : /* Bridges the integrator-installed getter (or the Null Object substituted at
855 : : * config-copy time) to the bounded handshake deadline. Invoked at the start
856 : : * of each handshake attempt so runtime-tunable values take effect on the next
857 : : * reconnect. */
858 : 241 : static inline uint32_t WolfSslStream_ResolveHandshakeTimeoutMs(struct SolidSyslogWolfSslStream* self)
859 : : {
860 : 241 : return self->Config.GetHandshakeTimeoutMs(self->Config.HandshakeTimeoutContext);
861 : : }
862 : :
863 : : /* Drive wolfSSL_connect to completion under non-blocking transport. Each call
864 : : * may want more I/O while waiting for the multi-RTT handshake to progress; the
865 : : * loop sleeps briefly between attempts (avoiding a busy spin) until either the
866 : : * handshake completes, hits a hard error, or the bounded budget expires. Each
867 : : * non-success exit emits a distinct code so the integrator can tell rejection
868 : : * from timeout. */
869 : 241 : static inline bool WolfSslStream_PerformHandshake(struct SolidSyslogWolfSslStream* self)
870 : : {
871 : 241 : uint32_t budgetMs = WolfSslStream_ResolveHandshakeTimeoutMs(self);
872 : 241 : uint32_t totalSleptMs = 0U;
873 : 241 : bool result = false;
874 : 241 : bool done = false;
875 : :
876 [ + + ]: 1494 : while (!done)
877 : : {
878 : 1253 : int rc = wolfSSL_connect(self->Session);
879 : 1253 : int error = wolfSSL_get_error(self->Session, rc);
880 [ + + ]: 1253 : if (rc == WOLFSSL_SUCCESS)
881 : : {
882 : 153 : result = WolfSslStream_PinIsConfirmed(self);
883 : 153 : done = true;
884 : : }
885 [ + + ]: 1100 : else if (!WolfSslStream_IsRetryableHandshakeError(error))
886 : : {
887 : 84 : WolfSslStream_ReportOpenFailure(
888 : : self,
889 : : SOLIDSYSLOG_SEVERITY_ERROR,
890 : : SOLIDSYSLOG_CAT_TLS_STREAM_HANDSHAKE_FAILED,
891 : : WolfSslStream_InitDetail(error, WolfSslStream_RefusalDetail(self))
892 : : );
893 : 84 : done = true;
894 : : }
895 [ + + ]: 1016 : else if (WolfSslStream_IsHandshakeBudgetExhausted(totalSleptMs, budgetMs))
896 : : {
897 : 4 : WolfSslStream_ReportOpenFailure(
898 : : self,
899 : : SOLIDSYSLOG_SEVERITY_WARNING,
900 : : SOLIDSYSLOG_CAT_TLS_STREAM_HANDSHAKE_FAILED,
901 : : SOLIDSYSLOG_TLS_STREAM_ERROR_HANDSHAKE_TIMEOUT
902 : : );
903 : 4 : done = true;
904 : : }
905 : : else
906 : : {
907 : 1012 : uint32_t pollMs = WolfSslStream_HandshakePollMs(totalSleptMs, budgetMs);
908 : 1012 : self->Config.Sleep(pollMs);
909 : 1012 : totalSleptMs += pollMs;
910 : : }
911 : : }
912 : 241 : return result;
913 : : }
914 : :
915 : : /* A pin is only as good as the check that read it, so a pinned peer whose
916 : : * handshake completed without a pin matching is refused rather than trusted. */
917 : 153 : static inline bool WolfSslStream_PinIsConfirmed(struct SolidSyslogWolfSslStream* self)
918 : : {
919 [ + + + + ]: 153 : if (WolfSslStream_IsPinned(self) && !self->PeerPinned)
920 : : {
921 : 25 : SolidSyslogWolfSslStream_AuthoriseKeptPeer(self);
922 : : }
923 [ + + + + ]: 153 : bool ok = !WolfSslStream_IsPinned(self) || self->PeerPinned;
924 [ + + ]: 153 : if (!ok)
925 : : {
926 : 21 : self->RefusedChecks |= WOLFSSL_STREAM_CHECK_FINGERPRINT;
927 : 21 : WolfSslStream_ReportOpenFailure(
928 : : self,
929 : : SOLIDSYSLOG_SEVERITY_ERROR,
930 : : SOLIDSYSLOG_CAT_TLS_STREAM_HANDSHAKE_FAILED,
931 : : WolfSslStream_RefusalDetail(self)
932 : : );
933 : : }
934 : 153 : return ok;
935 : : }
936 : :
937 : 1100 : static inline bool WolfSslStream_IsRetryableHandshakeError(int error)
938 : : {
939 [ + + + + ]: 1100 : return (error == WOLFSSL_ERROR_WANT_READ) || (error == WOLFSSL_ERROR_WANT_WRITE);
940 : : }
941 : :
942 : 1016 : static inline bool WolfSslStream_IsHandshakeBudgetExhausted(uint32_t totalSleptMs, uint32_t budgetMs)
943 : : {
944 : 1016 : return totalSleptMs >= budgetMs;
945 : : }
946 : :
947 : : /* The poll interval, or what is left of the budget where that is less, so the
948 : : * sleeps requested never add up to more than the budget. How long each sleep
949 : : * really takes, and the handshake itself, can still carry the attempt past it.
950 : : * Called only while budget remains. */
951 : 1012 : static inline uint32_t WolfSslStream_HandshakePollMs(uint32_t totalSleptMs, uint32_t budgetMs)
952 : : {
953 : 1012 : uint32_t pollMs = (uint32_t) HANDSHAKE_POLL_INTERVAL_MILLISECONDS;
954 : 1012 : uint32_t remainingMs = budgetMs - totalSleptMs;
955 [ + + ]: 1012 : if (remainingMs < pollMs)
956 : : {
957 : 1 : pollMs = remainingMs;
958 : : }
959 : 1012 : return pollMs;
960 : : }
961 : :
962 : : /* The checks accumulate, so a compound verdict resolves by precedence: an
963 : : * untrusted chain is reported ahead of anything the certificate says about
964 : : * itself, because a certificate no anchor vouches for is not made acceptable by
965 : : * the name or the dates it carries; and the dates ahead of the name, as the
966 : : * contract orders them. With no check recorded the handshake was rejected by
967 : : * the peer or the protocol, and no check of ours names it. */
968 : 105 : static inline enum SolidSyslogTlsStreamErrors WolfSslStream_RefusalDetail(const struct SolidSyslogWolfSslStream* self)
969 : : {
970 : 105 : enum SolidSyslogTlsStreamErrors detail = SOLIDSYSLOG_TLS_STREAM_ERROR_HANDSHAKE_REJECTED;
971 [ + + ]: 105 : if (WolfSslStream_Failed(self, WOLFSSL_STREAM_CHECK_FINGERPRINT_DIGEST))
972 : : {
973 : 4 : detail = SOLIDSYSLOG_TLS_STREAM_ERROR_FINGERPRINT_DIGEST_UNAVAILABLE;
974 : : }
975 [ + + ]: 101 : else if (WolfSslStream_Failed(self, WOLFSSL_STREAM_CHECK_FINGERPRINT))
976 : : {
977 : 26 : detail = SOLIDSYSLOG_TLS_STREAM_ERROR_PEER_FINGERPRINT_MISMATCHED;
978 : : }
979 [ + + ]: 75 : else if (WolfSslStream_Failed(self, WOLFSSL_STREAM_CHECK_CHAIN_TRUST))
980 : : {
981 : 23 : detail = SOLIDSYSLOG_TLS_STREAM_ERROR_PEER_CERTIFICATE_UNTRUSTED;
982 : : }
983 [ + + ]: 52 : else if (WolfSslStream_Failed(self, WOLFSSL_STREAM_CHECK_NOT_AFTER))
984 : : {
985 : 13 : detail = SOLIDSYSLOG_TLS_STREAM_ERROR_PEER_CERTIFICATE_EXPIRED;
986 : : }
987 [ + + ]: 39 : else if (WolfSslStream_Failed(self, WOLFSSL_STREAM_CHECK_NOT_BEFORE))
988 : : {
989 : 8 : detail = SOLIDSYSLOG_TLS_STREAM_ERROR_PEER_CERTIFICATE_NOT_YET_VALID;
990 : : }
991 [ + + ]: 31 : else if (WolfSslStream_Failed(self, WOLFSSL_STREAM_CHECK_NAME))
992 : : {
993 : 13 : detail = SOLIDSYSLOG_TLS_STREAM_ERROR_PEER_NAME_MISMATCHED;
994 : : }
995 : : else
996 : : {
997 : : /* Nothing recorded: the detail already holds the plain rejection. */
998 : : }
999 : 105 : return detail;
1000 : : }
1001 : :
1002 : 403 : static inline bool WolfSslStream_Failed(const struct SolidSyslogWolfSslStream* self, uint32_t check)
1003 : : {
1004 : 403 : return (self->RefusedChecks & check) != 0U;
1005 : : }
1006 : :
1007 : : /* TLS-level write failure means the session state is unrecoverable - close
1008 : : * so the StreamSender reconnect path runs on the next tick. Fail-fast is the
1009 : : * contract every TLS stream adapter honours. */
1010 : 20 : static inline bool WolfSslStream_Send(struct SolidSyslogStream* base, const void* buffer, size_t size)
1011 : : {
1012 : 20 : struct SolidSyslogWolfSslStream* self = WolfSslStream_SelfFromBase(base);
1013 : 20 : bool ok = false;
1014 [ + + ]: 20 : if (WolfSslStream_SessionIsStillOpen(base))
1015 : : {
1016 : 12 : int rc = wolfSSL_write(self->Session, buffer, (int) size);
1017 [ + + + + ]: 12 : ok = (rc > 0) && ((size_t) rc == size);
1018 [ + + ]: 12 : if (!ok)
1019 : : {
1020 : 5 : WolfSslStream_Close(base);
1021 : : }
1022 : : }
1023 : 20 : return ok;
1024 : : }
1025 : :
1026 : : /* wolfSSL_write does not look at what the peer has sent, so a collector that
1027 : : * has closed the session - a close_notify and a FIN - still has the record
1028 : : * accepted, and the transport beneath may not see the FIN behind the unread
1029 : : * alert. Read what is waiting first: an alert or a closed transport ends the
1030 : : * session here, and Read closes it. A collector sends no application data, so
1031 : : * any that arrives is discarded; a peer that keeps sending is plainly still
1032 : : * there, and the record goes out once the bound is reached. */
1033 : 20 : static bool WolfSslStream_SessionIsStillOpen(struct SolidSyslogStream* base)
1034 : : {
1035 : : unsigned char discard[WOLFSSL_STREAM_DISCARD_BYTES];
1036 : 20 : SolidSyslogSsize pending = WolfSslStream_Read(base, discard, sizeof(discard));
1037 : 20 : uint32_t reads = 1U;
1038 [ + + + + ]: 35 : while ((pending > 0) && (reads < WOLFSSL_STREAM_MAX_READS_BEFORE_WRITE))
1039 : : {
1040 : 15 : pending = WolfSslStream_Read(base, discard, sizeof(discard));
1041 : 15 : reads++;
1042 : : }
1043 : 20 : return pending >= 0;
1044 : : }
1045 : :
1046 : : /* wolfSSL_read has two distinct outcomes worth keeping straight:
1047 : : * 1. Steady-state read: bytes available -> positive count; nothing to read
1048 : : * right now -> WANT_READ -> return 0, mirroring the transport contract.
1049 : : * 2. Any other return (close_notify, a fatal alert, a hard transport error,
1050 : : * a session that is not open) is fatal under fail-fast semantics - close
1051 : : * internally; the caller reopens and store-and-forward replays. */
1052 : 42 : static inline SolidSyslogSsize WolfSslStream_Read(struct SolidSyslogStream* base, void* buffer, size_t size)
1053 : : {
1054 : 42 : struct SolidSyslogWolfSslStream* self = WolfSslStream_SelfFromBase(base);
1055 : 42 : int rc = wolfSSL_read(self->Session, buffer, (int) size);
1056 : 42 : SolidSyslogSsize result = -1;
1057 [ + + ]: 42 : if (rc > 0)
1058 : : {
1059 : 19 : result = (SolidSyslogSsize) rc;
1060 : : }
1061 [ + + ]: 23 : else if (wolfSSL_get_error(self->Session, rc) == WOLFSSL_ERROR_WANT_READ)
1062 : : {
1063 : 12 : result = 0;
1064 : : }
1065 : : else
1066 : : {
1067 : 11 : WolfSslStream_Close(base);
1068 : : }
1069 : 42 : return result;
1070 : : }
1071 : :
1072 : : /* cppcheck-suppress constParameterCallback -- wolfSSL's CallbackIOSend type declares buf without const. */
1073 : 362 : static int WolfSslStream_IoSend(WOLFSSL* ssl, char* buf, int sz, void* ctx)
1074 : : {
1075 : 362 : struct SolidSyslogWolfSslStream* self = (struct SolidSyslogWolfSslStream*) ctx;
1076 : : (void) ssl;
1077 [ + + ]: 362 : return SolidSyslogStream_Send(self->Config.Transport, buf, (size_t) sz) ? sz : WOLFSSL_CBIO_ERR_GENERAL;
1078 : : }
1079 : :
1080 : 799 : static int WolfSslStream_IoRecv(WOLFSSL* ssl, char* buf, int sz, void* ctx)
1081 : : {
1082 : 799 : struct SolidSyslogWolfSslStream* self = (struct SolidSyslogWolfSslStream*) ctx;
1083 : : (void) ssl;
1084 : 799 : SolidSyslogSsize n = SolidSyslogStream_Read(self->Config.Transport, buf, (size_t) sz);
1085 : 799 : int result = WOLFSSL_CBIO_ERR_GENERAL;
1086 [ + + ]: 799 : if (n > 0)
1087 : : {
1088 : 791 : result = (int) n;
1089 : : }
1090 [ + + ]: 8 : else if (n == 0)
1091 : : {
1092 : 4 : result = WOLFSSL_CBIO_ERR_WANT_READ;
1093 : : }
1094 : : else
1095 : : {
1096 : : /* Transport-level error; a general failure aborts the handshake / read. */
1097 : : }
1098 : 799 : return result;
1099 : : }
|