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 "SolidSyslogMbedTlsHmacSha256Policy.h"
6 : :
7 : : #include <mbedtls/md.h>
8 : : #include <mbedtls/platform_util.h>
9 : : #include <stdbool.h>
10 : : #include <stddef.h>
11 : : #include <stdint.h>
12 : :
13 : : #include "SolidSyslogError.h"
14 : : #include "SolidSyslogErrorCategory.h"
15 : : #include "SolidSyslogMbedTlsHmacSha256PolicyErrors.h"
16 : : #include "SolidSyslogMbedTlsHmacSha256PolicyPrivate.h"
17 : : #include "SolidSyslogPrival.h"
18 : : #include "SolidSyslogSecurityPolicyCategories.h"
19 : : #include "SolidSyslogSecurityPolicyDefinition.h"
20 : : #include "SolidSyslogTunables.h"
21 : :
22 : : const struct SolidSyslogErrorSource SolidSyslogMbedTlsHmacSha256PolicyErrorSource = {
23 : : "MbedTlsHmacSha256Policy",
24 : : SolidSyslogHmacSha256PolicyErrors_Name
25 : : };
26 : :
27 : : enum
28 : : {
29 : : HMAC_SHA256_TAG_SIZE = 32,
30 : : /* RFC 2104 / NIST SP 800-107: an HMAC key should be at least the hash
31 : : * output length. A shorter key (or an empty one) yields a well-defined but
32 : : * cryptographically worthless MAC, so we reject it and fail closed. */
33 : : HMAC_SHA256_MIN_KEY_SIZE = 32
34 : : };
35 : :
36 : : static inline struct SolidSyslogMbedTlsHmacSha256Policy* MbedTlsHmacSha256Policy_SelfFromBase(
37 : : struct SolidSyslogSecurityPolicy* base
38 : : );
39 : : static bool MbedTlsHmacSha256Policy_SealRecord(
40 : : struct SolidSyslogSecurityPolicy* base,
41 : : const struct SolidSyslogSecurityRecord* record
42 : : );
43 : : static enum SolidSyslogSecurityPolicyOpenResult MbedTlsHmacSha256Policy_OpenRecord(
44 : : struct SolidSyslogSecurityPolicy* base,
45 : : const struct SolidSyslogSecurityRecord* record
46 : : );
47 : : static bool MbedTlsHmacSha256Policy_ComputeTag(
48 : : struct SolidSyslogMbedTlsHmacSha256Policy* self,
49 : : const uint8_t* data,
50 : : uint16_t length,
51 : : uint8_t* tagOut,
52 : : uint16_t failureCategory
53 : : );
54 : : static bool MbedTlsHmacSha256Policy_FetchKey(
55 : : struct SolidSyslogMbedTlsHmacSha256Policy* self,
56 : : uint8_t* keyOut,
57 : : size_t* keyLengthOut
58 : : );
59 : : static void MbedTlsHmacSha256Policy_ReportFault(
60 : : struct SolidSyslogMbedTlsHmacSha256Policy* self,
61 : : uint16_t category,
62 : : enum SolidSyslogHmacSha256PolicyErrors code
63 : : );
64 : : static inline bool MbedTlsHmacSha256Policy_KeyLengthIsValid(size_t keyLength);
65 : : static inline bool MbedTlsHmacSha256Policy_ConstantTimeEquals(const uint8_t* a, const uint8_t* b, size_t length);
66 : :
67 : 29 : void SolidSyslogMbedTlsHmacSha256Policy_Initialise(
68 : : struct SolidSyslogSecurityPolicy* base,
69 : : const struct SolidSyslogMbedTlsHmacSha256PolicyConfig* config
70 : : )
71 : : {
72 : 29 : struct SolidSyslogMbedTlsHmacSha256Policy* self = MbedTlsHmacSha256Policy_SelfFromBase(base);
73 : 29 : self->Base.TrailerSize = HMAC_SHA256_TAG_SIZE;
74 : 29 : self->Base.SealRecord = MbedTlsHmacSha256Policy_SealRecord;
75 : 29 : self->Base.OpenRecord = MbedTlsHmacSha256Policy_OpenRecord;
76 : 29 : self->Config = *config;
77 : 29 : self->FaultReported = false;
78 : 29 : }
79 : :
80 : 29 : void SolidSyslogMbedTlsHmacSha256Policy_Cleanup(struct SolidSyslogSecurityPolicy* base)
81 : : {
82 : : /* No owned resources to release - the key is fetched on demand via the
83 : : * GetKey callback and never stored on the instance. */
84 : : (void) base;
85 : 29 : }
86 : :
87 : 69 : static inline struct SolidSyslogMbedTlsHmacSha256Policy* MbedTlsHmacSha256Policy_SelfFromBase(
88 : : struct SolidSyslogSecurityPolicy* base
89 : : )
90 : : {
91 : : /* Base is the first member of the instance struct - see Private.h. */
92 : 69 : return (struct SolidSyslogMbedTlsHmacSha256Policy*) base;
93 : : }
94 : :
95 : : /* HMAC authenticates the whole content as one buffer - the header/body split
96 : : * only matters to AEAD policies, so HeaderLength is ignored here. */
97 : 21 : static bool MbedTlsHmacSha256Policy_SealRecord(
98 : : struct SolidSyslogSecurityPolicy* base,
99 : : const struct SolidSyslogSecurityRecord* record
100 : : )
101 : : {
102 : : /* Bind the trailer to a local before passing it as the writable tag
103 : : * destination - same shape the AES-GCM sibling uses for its nonce/tag. */
104 : 21 : uint8_t* tag = record->Trailer;
105 : 21 : return MbedTlsHmacSha256Policy_ComputeTag(
106 : : MbedTlsHmacSha256Policy_SelfFromBase(base),
107 : 21 : record->Content,
108 : 21 : record->ContentLength,
109 : : tag,
110 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_SEAL_FAILED
111 : : );
112 : : }
113 : :
114 : : /* Fetches the key on demand into a transient buffer, computes HMAC-SHA256 over
115 : : * `data` into `tagOut`, then wipes the key buffer - the key never lingers
116 : : * beyond a single computation. Returns false and reports the reason if the key
117 : : * is unavailable or the HMAC computation fails: no tag, so a seal fails and an
118 : : * open could not run. Shared by seal (writes the record tag) and verify
119 : : * (recomputes for comparison). */
120 : 40 : static bool MbedTlsHmacSha256Policy_ComputeTag(
121 : : struct SolidSyslogMbedTlsHmacSha256Policy* self,
122 : : const uint8_t* data,
123 : : uint16_t length,
124 : : uint8_t* tagOut,
125 : : uint16_t failureCategory
126 : : )
127 : : {
128 : : uint8_t key[SOLIDSYSLOG_MAX_HMAC_KEY_SIZE];
129 : 40 : size_t keyLength = 0U;
130 : 40 : bool computed = false;
131 [ + + ]: 40 : if (MbedTlsHmacSha256Policy_FetchKey(self, key, &keyLength))
132 : : {
133 : 21 : const mbedtls_md_info_t* sha256 = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
134 [ + + ]: 21 : if (mbedtls_md_hmac(sha256, key, keyLength, data, length, tagOut) == 0)
135 : : {
136 : : /* A tag to seal with or verify against: the run of faults is over. */
137 : 17 : computed = true;
138 : 17 : self->FaultReported = false;
139 : : }
140 : : else
141 : : {
142 : 4 : MbedTlsHmacSha256Policy_ReportFault(
143 : : self,
144 : : failureCategory,
145 : : SOLIDSYSLOG_HMAC_SHA256_POLICY_ERROR_HMAC_FAILED
146 : : );
147 : : }
148 : : }
149 : : /* Wipe the whole key buffer - the full region GetKey was handed, not just
150 : : * the bytes written - so no key material lingers on the stack. */
151 : 40 : mbedtls_platform_zeroize(key, sizeof key);
152 : 40 : return computed;
153 : : }
154 : :
155 : : /* Fetches the key on demand and validates its length. Fails closed (and
156 : : * reports) if the key is unavailable, or if its length falls outside
157 : : * [HMAC_SHA256_MIN_KEY_SIZE, sizeof buffer]. */
158 : 40 : static bool MbedTlsHmacSha256Policy_FetchKey(
159 : : struct SolidSyslogMbedTlsHmacSha256Policy* self,
160 : : uint8_t* keyOut,
161 : : size_t* keyLengthOut
162 : : )
163 : : {
164 : 40 : size_t keyLength = 0U;
165 : 40 : bool fetched = false;
166 [ + + ]: 40 : if (self->Config.GetKey(self->Config.KeyContext, keyOut, SOLIDSYSLOG_MAX_HMAC_KEY_SIZE, &keyLength))
167 : : {
168 [ + + ]: 25 : if (MbedTlsHmacSha256Policy_KeyLengthIsValid(keyLength))
169 : : {
170 : 21 : *keyLengthOut = keyLength;
171 : 21 : fetched = true;
172 : : }
173 : : else
174 : : {
175 : 4 : MbedTlsHmacSha256Policy_ReportFault(
176 : : self,
177 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_KEY_UNAVAILABLE,
178 : : SOLIDSYSLOG_HMAC_SHA256_POLICY_ERROR_KEY_TOO_SHORT
179 : : );
180 : : }
181 : : }
182 : : else
183 : : {
184 : 15 : MbedTlsHmacSha256Policy_ReportFault(
185 : : self,
186 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_KEY_UNAVAILABLE,
187 : : SOLIDSYSLOG_HMAC_SHA256_POLICY_ERROR_KEY_UNAVAILABLE
188 : : );
189 : : }
190 : 40 : return fetched;
191 : : }
192 : :
193 : : /* The first fault in a run is the one reported. */
194 : 23 : static void MbedTlsHmacSha256Policy_ReportFault(
195 : : struct SolidSyslogMbedTlsHmacSha256Policy* self,
196 : : uint16_t category,
197 : : enum SolidSyslogHmacSha256PolicyErrors code
198 : : )
199 : : {
200 [ + + ]: 23 : if (!self->FaultReported)
201 : : {
202 : 20 : MbedTlsHmacSha256Policy_Report(SOLIDSYSLOG_SEVERITY_ERROR, category, code);
203 : 20 : self->FaultReported = true;
204 : : }
205 : 23 : }
206 : :
207 : 25 : static inline bool MbedTlsHmacSha256Policy_KeyLengthIsValid(size_t keyLength)
208 : : {
209 [ + + + + ]: 25 : return (keyLength >= (size_t) HMAC_SHA256_MIN_KEY_SIZE) && (keyLength <= (size_t) SOLIDSYSLOG_MAX_HMAC_KEY_SIZE);
210 : : }
211 : :
212 : 19 : static enum SolidSyslogSecurityPolicyOpenResult MbedTlsHmacSha256Policy_OpenRecord(
213 : : struct SolidSyslogSecurityPolicy* base,
214 : : const struct SolidSyslogSecurityRecord* record
215 : : )
216 : : {
217 : : uint8_t expected[HMAC_SHA256_TAG_SIZE];
218 : 19 : enum SolidSyslogSecurityPolicyOpenResult result = SOLIDSYSLOG_SECURITY_POLICY_OPEN_COULD_NOT_RUN;
219 [ + + ]: 19 : if (MbedTlsHmacSha256Policy_ComputeTag(
220 : : MbedTlsHmacSha256Policy_SelfFromBase(base),
221 : 19 : record->Content,
222 : 19 : record->ContentLength,
223 : : expected,
224 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_OPEN_FAILED
225 : : ))
226 : : {
227 : 4 : result = MbedTlsHmacSha256Policy_ConstantTimeEquals(expected, record->Trailer, HMAC_SHA256_TAG_SIZE)
228 : : ? SOLIDSYSLOG_SECURITY_POLICY_OPEN_OPENED
229 : 4 : : SOLIDSYSLOG_SECURITY_POLICY_OPEN_REJECTED;
230 : : }
231 : 19 : return result;
232 : : }
233 : :
234 : 4 : static inline bool MbedTlsHmacSha256Policy_ConstantTimeEquals(const uint8_t* a, const uint8_t* b, size_t length)
235 : : {
236 : : /* Accumulate every byte difference so the loop runs the full length
237 : : * regardless of where a mismatch occurs - no early exit, no timing oracle
238 : : * on the tag comparison. */
239 : 4 : uint8_t difference = 0U;
240 [ + + ]: 132 : for (size_t index = 0U; index < length; index++)
241 : : {
242 : 128 : difference |= (uint8_t) (a[index] ^ b[index]);
243 : : }
244 : 4 : return difference == 0U;
245 : : }
|