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