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 "SolidSyslogOpenSslAesGcmPolicy.h"
6 : :
7 : : #include <openssl/crypto.h>
8 : : #include <openssl/evp.h>
9 : : #include <openssl/rand.h>
10 : : #include <stdbool.h>
11 : : #include <stddef.h>
12 : : #include <stdint.h>
13 : : #include <string.h>
14 : :
15 : : #include "SolidSyslogError.h"
16 : : #include "SolidSyslogErrorCategory.h"
17 : : #include "SolidSyslogOpenSslAesGcmPolicyErrors.h"
18 : : #include "SolidSyslogOpenSslAesGcmPolicyPrivate.h"
19 : : #include "SolidSyslogPrival.h"
20 : : #include "SolidSyslogSecurityPolicyCategories.h"
21 : : #include "SolidSyslogSecurityPolicyDefinition.h"
22 : :
23 : : const struct SolidSyslogErrorSource SolidSyslogOpenSslAesGcmPolicyErrorSource = {
24 : : "OpenSslAesGcmPolicy",
25 : : SolidSyslogAesGcmPolicyErrors_Name
26 : : };
27 : :
28 : : enum
29 : : {
30 : : AES_256_KEY_SIZE = 32,
31 : : GCM_NONCE_SIZE = 12,
32 : : GCM_TAG_SIZE = 16,
33 : : /* Trailer is nonce || tag - fits SOLIDSYSLOG_MAX_INTEGRITY_SIZE (32). */
34 : : AES_GCM_TRAILER_SIZE = GCM_NONCE_SIZE + GCM_TAG_SIZE
35 : : };
36 : :
37 : : static inline struct SolidSyslogOpenSslAesGcmPolicy* OpenSslAesGcmPolicy_SelfFromBase(
38 : : struct SolidSyslogSecurityPolicy* base
39 : : );
40 : : static bool OpenSslAesGcmPolicy_SealRecord(
41 : : struct SolidSyslogSecurityPolicy* self,
42 : : const struct SolidSyslogSecurityRecord* record
43 : : );
44 : : static enum SolidSyslogSecurityPolicyOpenResult OpenSslAesGcmPolicy_OpenRecord(
45 : : struct SolidSyslogSecurityPolicy* self,
46 : : const struct SolidSyslogSecurityRecord* record
47 : : );
48 : : static bool OpenSslAesGcmPolicy_FetchKey(struct SolidSyslogOpenSslAesGcmPolicy* policy, uint8_t* keyOut);
49 : : static void OpenSslAesGcmPolicy_ReportFault(
50 : : struct SolidSyslogOpenSslAesGcmPolicy* policy,
51 : : uint16_t category,
52 : : enum SolidSyslogAesGcmPolicyErrors code
53 : : );
54 : : static bool OpenSslAesGcmPolicy_GcmEncrypt(
55 : : const uint8_t* key,
56 : : const uint8_t* nonce,
57 : : uint8_t* body,
58 : : uint16_t bodyLength,
59 : : const uint8_t* aad,
60 : : uint16_t aadLength,
61 : : uint8_t* tagOut
62 : : );
63 : : static enum SolidSyslogSecurityPolicyOpenResult OpenSslAesGcmPolicy_GcmDecrypt(
64 : : const uint8_t* key,
65 : : const uint8_t* nonce,
66 : : uint8_t* body,
67 : : uint16_t bodyLength,
68 : : const uint8_t* aad,
69 : : uint16_t aadLength,
70 : : const uint8_t* tagIn
71 : : );
72 : :
73 : 51 : void SolidSyslogOpenSslAesGcmPolicy_Initialise(
74 : : struct SolidSyslogSecurityPolicy* base,
75 : : const struct SolidSyslogOpenSslAesGcmPolicyConfig* config
76 : : )
77 : : {
78 : 51 : struct SolidSyslogOpenSslAesGcmPolicy* self = OpenSslAesGcmPolicy_SelfFromBase(base);
79 : 51 : self->Base.TrailerSize = AES_GCM_TRAILER_SIZE;
80 : 51 : self->Base.SealRecord = OpenSslAesGcmPolicy_SealRecord;
81 : 51 : self->Base.OpenRecord = OpenSslAesGcmPolicy_OpenRecord;
82 : 51 : self->Config = *config;
83 : 51 : self->FaultReported = false;
84 : 51 : }
85 : :
86 : 51 : void SolidSyslogOpenSslAesGcmPolicy_Cleanup(struct SolidSyslogSecurityPolicy* base)
87 : : {
88 : : /* No owned resources to release - the key is fetched on demand via the
89 : : * GetKey callback and never stored on the instance. */
90 : : (void) base;
91 : 51 : }
92 : :
93 : 119 : static inline struct SolidSyslogOpenSslAesGcmPolicy* OpenSslAesGcmPolicy_SelfFromBase(
94 : : struct SolidSyslogSecurityPolicy* base
95 : : )
96 : : {
97 : : /* Base is the first member of the instance struct - see Private.h. */
98 : 119 : return (struct SolidSyslogOpenSslAesGcmPolicy*) base;
99 : : }
100 : :
101 : : /* Seals in place: the nonce occupies Trailer[0..GCM_NONCE_SIZE) and the tag the
102 : : * remaining bytes. The header (Content[0..HeaderLength)) is authenticated as
103 : : * associated data and left in clear; the body (the rest) is encrypted in place.
104 : : * Fetches the key on demand and wipes it before returning. */
105 : 38 : static bool OpenSslAesGcmPolicy_SealRecord(
106 : : struct SolidSyslogSecurityPolicy* self,
107 : : const struct SolidSyslogSecurityRecord* record
108 : : )
109 : : {
110 : 38 : struct SolidSyslogOpenSslAesGcmPolicy* policy = OpenSslAesGcmPolicy_SelfFromBase(self);
111 : 38 : uint8_t* body = &record->Content[record->HeaderLength];
112 : 38 : uint16_t bodyLength = (uint16_t) (record->ContentLength - record->HeaderLength);
113 : 38 : uint8_t* nonce = record->Trailer;
114 : 38 : uint8_t* tag = &record->Trailer[GCM_NONCE_SIZE];
115 : :
116 : 38 : bool sealed = false;
117 : : uint8_t key[AES_256_KEY_SIZE];
118 [ + + ]: 38 : if (OpenSslAesGcmPolicy_FetchKey(policy, key))
119 : : {
120 [ + + ]: 32 : if (RAND_bytes(nonce, GCM_NONCE_SIZE) == 1)
121 : : {
122 [ + + ]: 30 : if (OpenSslAesGcmPolicy_GcmEncrypt(
123 : : key,
124 : : nonce,
125 : : body,
126 : : bodyLength,
127 : 30 : record->Content,
128 : 30 : record->HeaderLength,
129 : : tag
130 : : ))
131 : : {
132 : 22 : sealed = true;
133 : 22 : policy->FaultReported = false;
134 : : }
135 : : else
136 : : {
137 : 8 : OpenSslAesGcmPolicy_ReportFault(
138 : : policy,
139 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_SEAL_FAILED,
140 : : SOLIDSYSLOG_AES_GCM_POLICY_ERROR_ENCRYPT_FAILED
141 : : );
142 : : }
143 : : }
144 : : else
145 : : {
146 : 2 : OpenSslAesGcmPolicy_ReportFault(
147 : : policy,
148 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_SEAL_FAILED,
149 : : SOLIDSYSLOG_AES_GCM_POLICY_ERROR_NONCE_FAILED
150 : : );
151 : : }
152 : : }
153 : 38 : OPENSSL_cleanse(key, sizeof key);
154 : 38 : return sealed;
155 : : }
156 : :
157 : : /* Fetches the AES-256 key on demand. Fails closed (and reports) if the key is
158 : : * unavailable or not exactly 32 bytes - AES-256 admits no other key length. */
159 : 68 : static bool OpenSslAesGcmPolicy_FetchKey(struct SolidSyslogOpenSslAesGcmPolicy* policy, uint8_t* keyOut)
160 : : {
161 : 68 : size_t keyLength = 0U;
162 [ + + ]: 119 : bool fetched = policy->Config.GetKey(policy->Config.KeyContext, keyOut, AES_256_KEY_SIZE, &keyLength) &&
163 [ + + ]: 51 : (keyLength == (size_t) AES_256_KEY_SIZE);
164 [ + + ]: 68 : if (!fetched)
165 : : {
166 : 18 : OpenSslAesGcmPolicy_ReportFault(
167 : : policy,
168 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_KEY_UNAVAILABLE,
169 : : SOLIDSYSLOG_AES_GCM_POLICY_ERROR_KEY_UNAVAILABLE
170 : : );
171 : : }
172 : 68 : return fetched;
173 : : }
174 : :
175 : : /* The first fault in a run is the one reported. */
176 : 36 : static void OpenSslAesGcmPolicy_ReportFault(
177 : : struct SolidSyslogOpenSslAesGcmPolicy* policy,
178 : : uint16_t category,
179 : : enum SolidSyslogAesGcmPolicyErrors code
180 : : )
181 : : {
182 [ + + ]: 36 : if (!policy->FaultReported)
183 : : {
184 : 32 : OpenSslAesGcmPolicy_Report(SOLIDSYSLOG_SEVERITY_ERROR, category, code);
185 : 32 : policy->FaultReported = true;
186 : : }
187 : 36 : }
188 : :
189 : 30 : static bool OpenSslAesGcmPolicy_GcmEncrypt(
190 : : const uint8_t* key,
191 : : const uint8_t* nonce,
192 : : uint8_t* body,
193 : : uint16_t bodyLength,
194 : : const uint8_t* aad,
195 : : uint16_t aadLength,
196 : : uint8_t* tagOut
197 : : )
198 : : {
199 : 30 : bool ok = false;
200 : 30 : EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
201 [ + + ]: 30 : if (ctx != NULL)
202 : : {
203 : 29 : int outLength = 0;
204 : : /* GCM emits no bytes at final, so &body[outLength] is written zero bytes. */
205 [ + + ]: 57 : ok = (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) == 1) &&
206 [ + + ]: 55 : (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, GCM_NONCE_SIZE, NULL) == 1) &&
207 [ + + ]: 53 : (EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce) == 1) &&
208 [ + + ]: 51 : (EVP_EncryptUpdate(ctx, NULL, &outLength, aad, (int) aadLength) == 1) &&
209 [ + + ]: 49 : (EVP_EncryptUpdate(ctx, body, &outLength, body, (int) bodyLength) == 1) &&
210 [ + + + + ]: 81 : (EVP_EncryptFinal_ex(ctx, &body[outLength], &outLength) == 1) &&
211 : 23 : (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, GCM_TAG_SIZE, tagOut) == 1);
212 : 29 : EVP_CIPHER_CTX_free(ctx);
213 : : }
214 : 30 : return ok;
215 : : }
216 : :
217 : : /* Opens in place: decrypts the body and verifies the tag over the header
218 : : * (associated data) and ciphertext. Fetches the key on demand and wipes it. A
219 : : * tag mismatch is the expected tamper-detected outcome and is rejected
220 : : * silently; a missing key or a genuine OpenSSL error could not run, and is
221 : : * reported. */
222 : 30 : static enum SolidSyslogSecurityPolicyOpenResult OpenSslAesGcmPolicy_OpenRecord(
223 : : struct SolidSyslogSecurityPolicy* self,
224 : : const struct SolidSyslogSecurityRecord* record
225 : : )
226 : : {
227 : 30 : struct SolidSyslogOpenSslAesGcmPolicy* policy = OpenSslAesGcmPolicy_SelfFromBase(self);
228 : 30 : uint8_t* body = &record->Content[record->HeaderLength];
229 : 30 : uint16_t bodyLength = (uint16_t) (record->ContentLength - record->HeaderLength);
230 : 30 : const uint8_t* nonce = record->Trailer;
231 : 30 : const uint8_t* tag = &record->Trailer[GCM_NONCE_SIZE];
232 : :
233 : 30 : enum SolidSyslogSecurityPolicyOpenResult result = SOLIDSYSLOG_SECURITY_POLICY_OPEN_COULD_NOT_RUN;
234 : : uint8_t key[AES_256_KEY_SIZE];
235 [ + + ]: 30 : if (OpenSslAesGcmPolicy_FetchKey(policy, key))
236 : : {
237 : : result =
238 : 18 : OpenSslAesGcmPolicy_GcmDecrypt(key, nonce, body, bodyLength, record->Content, record->HeaderLength, tag);
239 [ + + ]: 18 : if (result == SOLIDSYSLOG_SECURITY_POLICY_OPEN_COULD_NOT_RUN)
240 : : {
241 : 8 : OpenSslAesGcmPolicy_ReportFault(
242 : : policy,
243 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_OPEN_FAILED,
244 : : SOLIDSYSLOG_AES_GCM_POLICY_ERROR_DECRYPT_FAILED
245 : : );
246 : : }
247 : : else
248 : : {
249 : : /* A verdict: the run of faults is over. */
250 : 10 : policy->FaultReported = false;
251 : : }
252 : : }
253 : 30 : OPENSSL_cleanse(key, sizeof key);
254 : 30 : return result;
255 : : }
256 : :
257 : 18 : static enum SolidSyslogSecurityPolicyOpenResult OpenSslAesGcmPolicy_GcmDecrypt(
258 : : const uint8_t* key,
259 : : const uint8_t* nonce,
260 : : uint8_t* body,
261 : : uint16_t bodyLength,
262 : : const uint8_t* aad,
263 : : uint16_t aadLength,
264 : : const uint8_t* tagIn
265 : : )
266 : : {
267 : : /* Copy the expected tag into a non-const buffer - EVP_CIPHER_CTX_ctrl's
268 : : * SET_TAG parameter is void*, and copying avoids casting away const. */
269 : : uint8_t tag[GCM_TAG_SIZE];
270 : 18 : (void) memcpy(tag, tagIn, sizeof tag);
271 : :
272 : : /* A tag mismatch is the expected tamper-detected outcome - rejected,
273 : : * like the HMAC verify. A genuine OpenSSL failure (context allocation or
274 : : * any setup step) reaches no verdict. */
275 : 18 : enum SolidSyslogSecurityPolicyOpenResult result = SOLIDSYSLOG_SECURITY_POLICY_OPEN_COULD_NOT_RUN;
276 : 18 : EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
277 [ + + ]: 18 : if (ctx != NULL)
278 : : {
279 : 16 : int outLength = 0;
280 [ + + ]: 31 : bool setUp = (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) == 1) &&
281 [ + + ]: 29 : (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, GCM_NONCE_SIZE, NULL) == 1) &&
282 [ + + ]: 27 : (EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce) == 1) &&
283 [ + + ]: 25 : (EVP_DecryptUpdate(ctx, NULL, &outLength, aad, (int) aadLength) == 1) &&
284 [ + + + + ]: 43 : (EVP_DecryptUpdate(ctx, body, &outLength, body, (int) bodyLength) == 1) &&
285 : 11 : (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, GCM_TAG_SIZE, tag) == 1);
286 [ + + ]: 16 : if (setUp)
287 : : {
288 : 10 : int finalLength = 0;
289 : 10 : result = (EVP_DecryptFinal_ex(ctx, &body[outLength], &finalLength) == 1)
290 : : ? SOLIDSYSLOG_SECURITY_POLICY_OPEN_OPENED
291 : 10 : : SOLIDSYSLOG_SECURITY_POLICY_OPEN_REJECTED;
292 : : }
293 : 16 : EVP_CIPHER_CTX_free(ctx);
294 : : }
295 : 18 : return result;
296 : : }
|