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 "SolidSyslogMbedTlsAesGcmPolicy.h"
6 : :
7 : : #include <mbedtls/cipher.h>
8 : : #include <mbedtls/ctr_drbg.h>
9 : : #include <mbedtls/gcm.h>
10 : : #include <mbedtls/platform_util.h>
11 : : #include <stdbool.h>
12 : : #include <stddef.h>
13 : : #include <stdint.h>
14 : :
15 : : #include "SolidSyslogError.h"
16 : : #include "SolidSyslogErrorCategory.h"
17 : : #include "SolidSyslogMbedTlsAesGcmPolicyErrors.h"
18 : : #include "SolidSyslogMbedTlsAesGcmPolicyPrivate.h"
19 : : #include "SolidSyslogPrival.h"
20 : : #include "SolidSyslogSecurityPolicyCategories.h"
21 : : #include "SolidSyslogSecurityPolicyDefinition.h"
22 : :
23 : : const struct SolidSyslogErrorSource SolidSyslogMbedTlsAesGcmPolicyErrorSource = {
24 : : "MbedTlsAesGcmPolicy",
25 : : SolidSyslogAesGcmPolicyErrors_Name
26 : : };
27 : :
28 : : enum
29 : : {
30 : : AES_256_KEY_SIZE = 32,
31 : : AES_256_KEY_BITS = 256,
32 : : GCM_NONCE_SIZE = 12,
33 : : GCM_TAG_SIZE = 16,
34 : : /* Trailer is nonce || tag - fits SOLIDSYSLOG_MAX_INTEGRITY_SIZE (32). */
35 : : AES_GCM_TRAILER_SIZE = GCM_NONCE_SIZE + GCM_TAG_SIZE
36 : : };
37 : :
38 : : static inline struct SolidSyslogMbedTlsAesGcmPolicy* MbedTlsAesGcmPolicy_SelfFromBase(
39 : : struct SolidSyslogSecurityPolicy* base
40 : : );
41 : : static bool MbedTlsAesGcmPolicy_SealRecord(
42 : : struct SolidSyslogSecurityPolicy* base,
43 : : const struct SolidSyslogSecurityRecord* record
44 : : );
45 : : static enum SolidSyslogSecurityPolicyOpenResult MbedTlsAesGcmPolicy_OpenRecord(
46 : : struct SolidSyslogSecurityPolicy* base,
47 : : const struct SolidSyslogSecurityRecord* record
48 : : );
49 : : static bool MbedTlsAesGcmPolicy_FetchKey(struct SolidSyslogMbedTlsAesGcmPolicy* self, uint8_t* keyOut);
50 : : static void MbedTlsAesGcmPolicy_ReportFault(
51 : : struct SolidSyslogMbedTlsAesGcmPolicy* self,
52 : : uint16_t category,
53 : : enum SolidSyslogAesGcmPolicyErrors code
54 : : );
55 : : static bool MbedTlsAesGcmPolicy_GcmEncrypt(const struct SolidSyslogSecurityRecord* record, const uint8_t* key);
56 : : static enum SolidSyslogSecurityPolicyOpenResult MbedTlsAesGcmPolicy_GcmDecrypt(
57 : : const struct SolidSyslogSecurityRecord* record,
58 : : const uint8_t* key
59 : : );
60 : :
61 : 40 : void SolidSyslogMbedTlsAesGcmPolicy_Initialise(
62 : : struct SolidSyslogSecurityPolicy* base,
63 : : const struct SolidSyslogMbedTlsAesGcmPolicyConfig* config
64 : : )
65 : : {
66 : 40 : struct SolidSyslogMbedTlsAesGcmPolicy* self = MbedTlsAesGcmPolicy_SelfFromBase(base);
67 : 40 : self->Base.TrailerSize = AES_GCM_TRAILER_SIZE;
68 : 40 : self->Base.SealRecord = MbedTlsAesGcmPolicy_SealRecord;
69 : 40 : self->Base.OpenRecord = MbedTlsAesGcmPolicy_OpenRecord;
70 : 40 : self->Config = *config;
71 : 40 : self->FaultReported = false;
72 : 40 : }
73 : :
74 : 40 : void SolidSyslogMbedTlsAesGcmPolicy_Cleanup(struct SolidSyslogSecurityPolicy* base)
75 : : {
76 : : /* No owned resources to release - the key is fetched on demand via the
77 : : * GetKey callback and never stored on the instance, and the CTR-DRBG is
78 : : * caller-owned. */
79 : : (void) base;
80 : 40 : }
81 : :
82 : 97 : static inline struct SolidSyslogMbedTlsAesGcmPolicy* MbedTlsAesGcmPolicy_SelfFromBase(
83 : : struct SolidSyslogSecurityPolicy* base
84 : : )
85 : : {
86 : : /* Base is the first member of the instance struct - see Private.h. */
87 : 97 : return (struct SolidSyslogMbedTlsAesGcmPolicy*) base;
88 : : }
89 : :
90 : : /* Seals in place: draws a fresh nonce into Trailer[0..GCM_NONCE_SIZE), then
91 : : * GcmEncrypt encrypts the body (Content past HeaderLength) and writes the tag to
92 : : * the remaining trailer bytes, authenticating the header (Content[0..HeaderLength))
93 : : * as associated data. Fetches the key on demand and wipes it before returning. */
94 : 32 : static bool MbedTlsAesGcmPolicy_SealRecord(
95 : : struct SolidSyslogSecurityPolicy* base,
96 : : const struct SolidSyslogSecurityRecord* record
97 : : )
98 : : {
99 : 32 : struct SolidSyslogMbedTlsAesGcmPolicy* self = MbedTlsAesGcmPolicy_SelfFromBase(base);
100 : :
101 : 32 : bool sealed = false;
102 : : uint8_t key[AES_256_KEY_SIZE];
103 [ + + ]: 32 : if (MbedTlsAesGcmPolicy_FetchKey(self, key))
104 : : {
105 : : /* Draw the per-record nonce straight into the trailer; GcmEncrypt reads
106 : : * it back from there and writes the tag immediately after it. */
107 [ + + ]: 26 : if (mbedtls_ctr_drbg_random(self->Config.Rng, record->Trailer, GCM_NONCE_SIZE) == 0)
108 : : {
109 [ + + ]: 24 : if (MbedTlsAesGcmPolicy_GcmEncrypt(record, key))
110 : : {
111 : 22 : sealed = true;
112 : 22 : self->FaultReported = false;
113 : : }
114 : : else
115 : : {
116 : 2 : MbedTlsAesGcmPolicy_ReportFault(
117 : : self,
118 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_SEAL_FAILED,
119 : : SOLIDSYSLOG_AES_GCM_POLICY_ERROR_ENCRYPT_FAILED
120 : : );
121 : : }
122 : : }
123 : : else
124 : : {
125 : 2 : MbedTlsAesGcmPolicy_ReportFault(
126 : : self,
127 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_SEAL_FAILED,
128 : : SOLIDSYSLOG_AES_GCM_POLICY_ERROR_NONCE_FAILED
129 : : );
130 : : }
131 : : }
132 : 32 : mbedtls_platform_zeroize(key, sizeof key);
133 : 32 : return sealed;
134 : : }
135 : :
136 : : /* Fetches the AES-256 key on demand. Fails closed (and reports) if the key is
137 : : * unavailable or not exactly 32 bytes - AES-256 admits no other key length. */
138 : 57 : static bool MbedTlsAesGcmPolicy_FetchKey(struct SolidSyslogMbedTlsAesGcmPolicy* self, uint8_t* keyOut)
139 : : {
140 : 57 : size_t keyLength = 0U;
141 [ + + ]: 97 : bool fetched = self->Config.GetKey(self->Config.KeyContext, keyOut, AES_256_KEY_SIZE, &keyLength) &&
142 [ + + ]: 40 : (keyLength == (size_t) AES_256_KEY_SIZE);
143 [ + + ]: 57 : if (!fetched)
144 : : {
145 : 18 : MbedTlsAesGcmPolicy_ReportFault(
146 : : self,
147 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_KEY_UNAVAILABLE,
148 : : SOLIDSYSLOG_AES_GCM_POLICY_ERROR_KEY_UNAVAILABLE
149 : : );
150 : : }
151 : 57 : return fetched;
152 : : }
153 : :
154 : : /* The first fault in a run is the one reported. */
155 : 25 : static void MbedTlsAesGcmPolicy_ReportFault(
156 : : struct SolidSyslogMbedTlsAesGcmPolicy* self,
157 : : uint16_t category,
158 : : enum SolidSyslogAesGcmPolicyErrors code
159 : : )
160 : : {
161 [ + + ]: 25 : if (!self->FaultReported)
162 : : {
163 : 21 : MbedTlsAesGcmPolicy_Report(SOLIDSYSLOG_SEVERITY_ERROR, category, code);
164 : 21 : self->FaultReported = true;
165 : : }
166 : 25 : }
167 : :
168 : : /* Encrypts the record's body in place and writes nonce||tag into its trailer.
169 : : * Takes the whole record (not the unpacked buffers) so key and nonce never sit
170 : : * adjacent as same-typed scalar parameters; the trailer/header layout lives in
171 : : * one place. The nonce is expected already in Trailer[0..GCM_NONCE_SIZE). One-
172 : : * shot AEAD - mbedTLS computes the whole tag in a single call (output == input
173 : : * is permitted for GCM encryption). */
174 : 24 : static bool MbedTlsAesGcmPolicy_GcmEncrypt(const struct SolidSyslogSecurityRecord* record, const uint8_t* key)
175 : : {
176 : 24 : uint8_t* body = &record->Content[record->HeaderLength];
177 : 24 : uint16_t bodyLength = (uint16_t) (record->ContentLength - record->HeaderLength);
178 : :
179 : : mbedtls_gcm_context ctx;
180 : 24 : mbedtls_gcm_init(&ctx);
181 : 24 : bool ok = false;
182 [ + + ]: 24 : if (mbedtls_gcm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, AES_256_KEY_BITS) == 0)
183 : : {
184 : 23 : int rc = mbedtls_gcm_crypt_and_tag(
185 : : &ctx,
186 : : MBEDTLS_GCM_ENCRYPT,
187 : : bodyLength,
188 : 23 : record->Trailer,
189 : : GCM_NONCE_SIZE,
190 : 23 : record->Content,
191 : 23 : record->HeaderLength,
192 : : body,
193 : : body,
194 : : GCM_TAG_SIZE,
195 : 23 : &record->Trailer[GCM_NONCE_SIZE]
196 : : );
197 : 23 : ok = (rc == 0);
198 : : }
199 : 24 : mbedtls_gcm_free(&ctx);
200 : 24 : return ok;
201 : : }
202 : :
203 : : /* Opens in place: decrypts the body and verifies the tag over the header
204 : : * (associated data) and ciphertext. Fetches the key on demand and wipes it. A
205 : : * tag mismatch is the expected tamper-detected outcome and is rejected
206 : : * silently; a missing key or a genuine mbedTLS error could not run, and is
207 : : * reported. */
208 : 25 : static enum SolidSyslogSecurityPolicyOpenResult MbedTlsAesGcmPolicy_OpenRecord(
209 : : struct SolidSyslogSecurityPolicy* base,
210 : : const struct SolidSyslogSecurityRecord* record
211 : : )
212 : : {
213 : 25 : struct SolidSyslogMbedTlsAesGcmPolicy* self = MbedTlsAesGcmPolicy_SelfFromBase(base);
214 : :
215 : 25 : enum SolidSyslogSecurityPolicyOpenResult result = SOLIDSYSLOG_SECURITY_POLICY_OPEN_COULD_NOT_RUN;
216 : : uint8_t key[AES_256_KEY_SIZE];
217 [ + + ]: 25 : if (MbedTlsAesGcmPolicy_FetchKey(self, key))
218 : : {
219 : 13 : result = MbedTlsAesGcmPolicy_GcmDecrypt(record, key);
220 [ + + ]: 13 : if (result == SOLIDSYSLOG_SECURITY_POLICY_OPEN_COULD_NOT_RUN)
221 : : {
222 : 3 : MbedTlsAesGcmPolicy_ReportFault(
223 : : self,
224 : : SOLIDSYSLOG_CAT_SECURITY_POLICY_OPEN_FAILED,
225 : : SOLIDSYSLOG_AES_GCM_POLICY_ERROR_DECRYPT_FAILED
226 : : );
227 : : }
228 : : else
229 : : {
230 : : /* A verdict: the run of faults is over. */
231 : 10 : self->FaultReported = false;
232 : : }
233 : : }
234 : 25 : mbedtls_platform_zeroize(key, sizeof key);
235 : 25 : return result;
236 : : }
237 : :
238 : : /* Decrypts the record's body in place, reading nonce||tag from its trailer and
239 : : * authenticating the header. Takes the whole record for the same reasons as
240 : : * GcmEncrypt. mbedtls_gcm_auth_decrypt verifies the tag itself and signals the
241 : : * verdict through its return code: 0 = authentic, GCM_AUTH_FAILED = tamper /
242 : : * wrong key (the expected rejection - silent, like the HMAC verify), anything
243 : : * else = a genuine mbedTLS error, which reaches no verdict. */
244 : 13 : static enum SolidSyslogSecurityPolicyOpenResult MbedTlsAesGcmPolicy_GcmDecrypt(
245 : : const struct SolidSyslogSecurityRecord* record,
246 : : const uint8_t* key
247 : : )
248 : : {
249 : 13 : uint8_t* body = &record->Content[record->HeaderLength];
250 : 13 : uint16_t bodyLength = (uint16_t) (record->ContentLength - record->HeaderLength);
251 : :
252 : : mbedtls_gcm_context ctx;
253 : 13 : mbedtls_gcm_init(&ctx);
254 : 13 : enum SolidSyslogSecurityPolicyOpenResult result = SOLIDSYSLOG_SECURITY_POLICY_OPEN_COULD_NOT_RUN;
255 [ + + ]: 13 : if (mbedtls_gcm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, AES_256_KEY_BITS) == 0)
256 : : {
257 : 11 : int verdict = mbedtls_gcm_auth_decrypt(
258 : : &ctx,
259 : : bodyLength,
260 : 11 : record->Trailer,
261 : : GCM_NONCE_SIZE,
262 : 11 : record->Content,
263 : 11 : record->HeaderLength,
264 : 11 : &record->Trailer[GCM_NONCE_SIZE],
265 : : GCM_TAG_SIZE,
266 : : body,
267 : : body
268 : : );
269 [ + + ]: 11 : if (verdict == 0)
270 : : {
271 : 4 : result = SOLIDSYSLOG_SECURITY_POLICY_OPEN_OPENED;
272 : : }
273 [ + + ]: 7 : else if (verdict == MBEDTLS_ERR_GCM_AUTH_FAILED)
274 : : {
275 : 6 : result = SOLIDSYSLOG_SECURITY_POLICY_OPEN_REJECTED;
276 : : }
277 : : else
278 : : {
279 : : /* Genuine mbedTLS failure - no verdict. */
280 : : }
281 : : }
282 : 13 : mbedtls_gcm_free(&ctx);
283 : 13 : return result;
284 : : }
|