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 "SolidSyslogTlsFingerprint.h"
6 : :
7 : : #include "SolidSyslogMacros.h"
8 : :
9 : : enum
10 : : {
11 : : TLS_FINGERPRINT_SHA1_LENGTH = 20U,
12 : : TLS_FINGERPRINT_SHA256_LENGTH = 32U
13 : : };
14 : :
15 : : /* Adding an algorithm to SolidSyslogTlsHashAlgorithm and to each backend's
16 : : * digest map looks complete, and would overrun every caller's buffer without
17 : : * this: a backend writes its digest into DIGEST_MAX bytes on the stack. */
18 : : SOLIDSYSLOG_STATIC_ASSERT(
19 : : (size_t) TLS_FINGERPRINT_SHA256_LENGTH <= (size_t) SOLIDSYSLOG_TLS_FINGERPRINT_DIGEST_MAX,
20 : : Longest supported digest must fit SOLIDSYSLOG_TLS_FINGERPRINT_DIGEST_MAX
21 : : );
22 : :
23 : : struct SolidSyslogTlsFingerprintLabel
24 : : {
25 : : const char* Label;
26 : : enum SolidSyslogTlsHashAlgorithm Algorithm;
27 : : size_t Length;
28 : : };
29 : :
30 : : static inline bool TlsFingerprint_ParseLabel(
31 : : const char* text,
32 : : const char** digestText,
33 : : struct SolidSyslogTlsFingerprint* out
34 : : );
35 : : static inline bool TlsFingerprint_LabelMatches(const char* text, const char* label, const char** digestText);
36 : : static inline bool TlsFingerprint_ParseHexPairs(const char* text, uint8_t* digest, size_t length);
37 : : static inline bool TlsFingerprint_ParseHexPair(const char* text, uint8_t* value);
38 : : static inline bool TlsFingerprint_ParseHexDigit(char digit, uint8_t* value);
39 : : static inline enum SolidSyslogTlsFingerprintListState TlsFingerprint_InspectOne(const char* text);
40 : : static inline bool TlsFingerprint_ListIsReadable(const char* const * fingerprints, size_t count);
41 : : static inline enum SolidSyslogTlsAuthorisation TlsFingerprint_AuthoriseOne(
42 : : const char* fingerprint,
43 : : SolidSyslogTlsDigestFunction digest,
44 : : void* context
45 : : );
46 : : static inline bool TlsFingerprint_DigestEquals(
47 : : const struct SolidSyslogTlsFingerprint* pin,
48 : : const uint8_t* digest,
49 : : size_t length
50 : : );
51 : :
52 : 365 : bool SolidSyslogTlsFingerprint_Parse(const char* text, struct SolidSyslogTlsFingerprint* out)
53 : : {
54 : 365 : const char* digestText = NULL;
55 [ + + + + : 365 : bool parsed = (text != NULL) && (out != NULL) && TlsFingerprint_ParseLabel(text, &digestText, out);
+ + ]
56 : :
57 [ + + ]: 365 : if (parsed)
58 : : {
59 : 358 : parsed = TlsFingerprint_ParseHexPairs(digestText, out->Digest, out->Length);
60 : : }
61 : :
62 : 365 : return parsed;
63 : : }
64 : :
65 : 361 : static inline bool TlsFingerprint_ParseLabel(
66 : : const char* text,
67 : : const char** digestText,
68 : : struct SolidSyslogTlsFingerprint* out
69 : : )
70 : : {
71 : : static const struct SolidSyslogTlsFingerprintLabel TLS_FINGERPRINT_LABELS[] = {
72 : : {"sha-1", SOLIDSYSLOG_TLS_HASH_SHA1, TLS_FINGERPRINT_SHA1_LENGTH},
73 : : {"sha-256", SOLIDSYSLOG_TLS_HASH_SHA256, TLS_FINGERPRINT_SHA256_LENGTH},
74 : : };
75 : 361 : bool parsed = false;
76 : 361 : size_t count = sizeof(TLS_FINGERPRINT_LABELS) / sizeof(TLS_FINGERPRINT_LABELS[0]);
77 : :
78 [ + + + + ]: 1035 : for (size_t i = 0U; (i < count) && !parsed; i++)
79 : : {
80 [ + + ]: 674 : if (TlsFingerprint_LabelMatches(text, TLS_FINGERPRINT_LABELS[i].Label, digestText))
81 : : {
82 : 358 : out->Algorithm = TLS_FINGERPRINT_LABELS[i].Algorithm;
83 : 358 : out->Length = TLS_FINGERPRINT_LABELS[i].Length;
84 : 358 : parsed = true;
85 : : }
86 : : }
87 : :
88 : 361 : return parsed;
89 : : }
90 : :
91 : 674 : static inline bool TlsFingerprint_LabelMatches(const char* text, const char* label, const char** digestText)
92 : : {
93 : 674 : size_t i = 0U;
94 : :
95 [ + + + + ]: 4342 : while ((label[i] != '\0') && (text[i] == label[i]))
96 : : {
97 : 3668 : i++;
98 : : }
99 : :
100 [ + + + + ]: 674 : bool matches = (label[i] == '\0') && (text[i] == ':');
101 : :
102 [ + + ]: 674 : if (matches)
103 : : {
104 : 358 : *digestText = &text[i + 1U];
105 : : }
106 : :
107 : 674 : return matches;
108 : : }
109 : :
110 : 358 : static inline bool TlsFingerprint_ParseHexPairs(const char* text, uint8_t* digest, size_t length)
111 : : {
112 : 358 : bool parsed = true;
113 : 358 : size_t position = 0U;
114 : :
115 [ + + + + ]: 10625 : for (size_t i = 0U; (i < length) && parsed; i++)
116 : : {
117 : 10267 : parsed = TlsFingerprint_ParseHexPair(&text[position], &digest[i]);
118 : 10267 : position += 2U;
119 : :
120 [ + + + + ]: 10267 : if (parsed && ((i + 1U) < length))
121 : : {
122 : 9917 : parsed = (text[position] == ':');
123 : 9917 : position++;
124 : : }
125 : : }
126 : :
127 [ + + + + ]: 358 : return parsed && (text[position] == '\0');
128 : : }
129 : :
130 : 10267 : static inline bool TlsFingerprint_ParseHexPair(const char* text, uint8_t* value)
131 : : {
132 : 10267 : uint8_t high = 0U;
133 : 10267 : uint8_t low = 0U;
134 [ + + + + ]: 10267 : bool parsed = TlsFingerprint_ParseHexDigit(text[0], &high) && TlsFingerprint_ParseHexDigit(text[1], &low);
135 : :
136 [ + + ]: 10267 : if (parsed)
137 : : {
138 : 10250 : *value = (uint8_t) ((uint8_t) (high << 4U) | low);
139 : : }
140 : :
141 : 10267 : return parsed;
142 : : }
143 : :
144 : 20518 : static inline bool TlsFingerprint_ParseHexDigit(char digit, uint8_t* value)
145 : : {
146 : 20518 : bool parsed = true;
147 : :
148 [ + + + + ]: 20518 : if ((digit >= '0') && (digit <= '9'))
149 : : {
150 : 14284 : *value = (uint8_t) ((uint8_t) digit - (uint8_t) '0');
151 : : }
152 [ + + + + ]: 6234 : else if ((digit >= 'A') && (digit <= 'F'))
153 : : {
154 : 6172 : *value = (uint8_t) (((uint8_t) digit - (uint8_t) 'A') + 10U);
155 : : }
156 [ + + + + ]: 62 : else if ((digit >= 'a') && (digit <= 'f'))
157 : : {
158 : 45 : *value = (uint8_t) (((uint8_t) digit - (uint8_t) 'a') + 10U);
159 : : }
160 : : else
161 : : {
162 : 17 : parsed = false;
163 : : }
164 : :
165 : 20518 : return parsed;
166 : : }
167 : :
168 : 315 : bool SolidSyslogTlsFingerprint_ListIsPresent(const char* const * fingerprints, size_t count)
169 : : {
170 : 315 : bool present = TlsFingerprint_ListIsReadable(fingerprints, count);
171 : :
172 [ + + + + ]: 406 : for (size_t i = 0U; present && (i < count); i++)
173 : : {
174 : 91 : present = fingerprints[i] != NULL;
175 : : }
176 : :
177 : 315 : return present;
178 : : }
179 : :
180 : 1143 : static inline bool TlsFingerprint_ListIsReadable(const char* const * fingerprints, size_t count)
181 : : {
182 [ + + + + ]: 1143 : return (count == 0U) || (fingerprints != NULL);
183 : : }
184 : :
185 : 662 : enum SolidSyslogTlsFingerprintListState SolidSyslogTlsFingerprint_InspectList(
186 : : const char* const * fingerprints,
187 : : size_t count
188 : : )
189 : : {
190 : 662 : enum SolidSyslogTlsFingerprintListState state = SOLIDSYSLOG_TLS_FINGERPRINT_LIST_WELL_FORMED;
191 : :
192 [ + + ]: 662 : if (TlsFingerprint_ListIsReadable(fingerprints, count))
193 : : {
194 [ + + ]: 829 : for (size_t i = 0U; i < count; i++)
195 : : {
196 : 168 : enum SolidSyslogTlsFingerprintListState one = TlsFingerprint_InspectOne(fingerprints[i]);
197 [ + + ]: 168 : if (one > state)
198 : : {
199 : 26 : state = one;
200 : : }
201 : : }
202 : : }
203 : : else
204 : : {
205 : 1 : state = SOLIDSYSLOG_TLS_FINGERPRINT_LIST_MALFORMED;
206 : : }
207 : :
208 : 662 : return state;
209 : : }
210 : :
211 : 168 : static inline enum SolidSyslogTlsFingerprintListState TlsFingerprint_InspectOne(const char* text)
212 : : {
213 : : struct SolidSyslogTlsFingerprint parsed;
214 : 168 : enum SolidSyslogTlsFingerprintListState state = SOLIDSYSLOG_TLS_FINGERPRINT_LIST_MALFORMED;
215 : :
216 [ + + ]: 168 : if (SolidSyslogTlsFingerprint_Parse(text, &parsed))
217 : : {
218 : 154 : state = (parsed.Algorithm == SOLIDSYSLOG_TLS_HASH_SHA1) ? SOLIDSYSLOG_TLS_FINGERPRINT_LIST_USES_SHA1
219 : 154 : : SOLIDSYSLOG_TLS_FINGERPRINT_LIST_WELL_FORMED;
220 : : }
221 : :
222 : 168 : return state;
223 : : }
224 : :
225 : 166 : enum SolidSyslogTlsAuthorisation SolidSyslogTlsFingerprint_Authorise(
226 : : const char* const * fingerprints,
227 : : size_t count,
228 : : SolidSyslogTlsDigestFunction digest,
229 : : void* context
230 : : )
231 : : {
232 : 166 : enum SolidSyslogTlsAuthorisation verdict = SOLIDSYSLOG_TLS_AUTHORISATION_NO_MATCH;
233 : :
234 [ + + ]: 166 : if (!TlsFingerprint_ListIsReadable(fingerprints, count))
235 : : {
236 : 1 : verdict = SOLIDSYSLOG_TLS_AUTHORISATION_MALFORMED;
237 : : }
238 : :
239 : 166 : bool anySkipped = false;
240 [ + + + + ]: 339 : for (size_t i = 0U; (i < count) && (verdict == SOLIDSYSLOG_TLS_AUTHORISATION_NO_MATCH); i++)
241 : : {
242 : 173 : verdict = TlsFingerprint_AuthoriseOne(fingerprints[i], digest, context);
243 [ + + ]: 173 : if (verdict == SOLIDSYSLOG_TLS_AUTHORISATION_DIGEST_UNAVAILABLE)
244 : : {
245 : : /* A hash this build cannot compute is that pin's problem and not
246 : : * the list's. Pinning the old certificate beside the new is how a
247 : : * fleet crosses a renewal, and the two may name different hashes,
248 : : * so stopping here would refuse a peer a later pin authorises. */
249 : 11 : anySkipped = true;
250 : 11 : verdict = SOLIDSYSLOG_TLS_AUTHORISATION_NO_MATCH;
251 : : }
252 : : }
253 [ + + + + ]: 166 : if ((verdict == SOLIDSYSLOG_TLS_AUTHORISATION_NO_MATCH) && anySkipped)
254 : : {
255 : : /* Nothing matched and a pin went uncompared, so the digest this build
256 : : * lacks is the fault worth naming - not a mismatch the integrator
257 : : * would go looking for on the collector. */
258 : 10 : verdict = SOLIDSYSLOG_TLS_AUTHORISATION_DIGEST_UNAVAILABLE;
259 : : }
260 : :
261 : 166 : return verdict;
262 : : }
263 : :
264 : 173 : static inline enum SolidSyslogTlsAuthorisation TlsFingerprint_AuthoriseOne(
265 : : const char* fingerprint,
266 : : SolidSyslogTlsDigestFunction digest,
267 : : void* context
268 : : )
269 : : {
270 : 173 : enum SolidSyslogTlsAuthorisation verdict = SOLIDSYSLOG_TLS_AUTHORISATION_MALFORMED;
271 : : struct SolidSyslogTlsFingerprint pin;
272 : :
273 [ + + ]: 173 : if (SolidSyslogTlsFingerprint_Parse(fingerprint, &pin))
274 : : {
275 : : uint8_t peerDigest[SOLIDSYSLOG_TLS_FINGERPRINT_DIGEST_MAX];
276 : 171 : size_t peerLength = 0U;
277 : 171 : verdict = SOLIDSYSLOG_TLS_AUTHORISATION_DIGEST_UNAVAILABLE;
278 : :
279 [ + + ]: 171 : if (digest(context, pin.Algorithm, peerDigest, &peerLength))
280 : : {
281 : 160 : verdict = TlsFingerprint_DigestEquals(&pin, peerDigest, peerLength)
282 : : ? SOLIDSYSLOG_TLS_AUTHORISATION_MATCHED
283 : 160 : : SOLIDSYSLOG_TLS_AUTHORISATION_NO_MATCH;
284 : : }
285 : : }
286 : :
287 : 173 : return verdict;
288 : : }
289 : :
290 : : /* Constant time: every byte is compared regardless of the first difference. */
291 : 160 : static inline bool TlsFingerprint_DigestEquals(
292 : : const struct SolidSyslogTlsFingerprint* pin,
293 : : const uint8_t* digest,
294 : : size_t length
295 : : )
296 : : {
297 : 160 : bool equal = length == pin->Length;
298 : :
299 [ + + ]: 160 : if (equal)
300 : : {
301 : 156 : uint8_t difference = 0U;
302 : :
303 [ + + ]: 4956 : for (size_t i = 0U; i < length; i++)
304 : : {
305 : 4800 : difference |= (uint8_t) (pin->Digest[i] ^ digest[i]);
306 : : }
307 : :
308 : 156 : equal = difference == 0U;
309 : : }
310 : :
311 : 160 : return equal;
312 : : }
|