ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
aes128gcm.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file aes128gcm.h
6 * @brief AEAD_AES_128_GCM (RFC 5116) - keyed, detached tag - and the AES-128 block it is built on.
7 *
8 * The shared 128-bit AES primitives for the whole library: the AEAD, and one 16-byte ECB block under a
9 * key of its own (QUIC header protection per RFC 9001 sec 5.4, the DTLS 1.3 sequence-number mask).
10 * Consumed by QUIC Initial packet protection (RFC 9001 sec 5.3/5.4), the DTLS 1.3 record layer, the TLS
11 * 1.3 record layer, and SMB 3.x transport encryption. The entries below are one surface over both arms:
12 * the GCM construction (GCTR and a table GHASH) is software on both, and only the AES-128 block under it
13 * changes, the part's AES accelerator where there is one and software AES-128 where there is not.
14 *
15 * The entries are keyed: @ref Aes128GcmNs::key_init once from the 16-byte key, then
16 * @ref Aes128GcmNs::seal or @ref Aes128GcmNs::open per record against it. There is no raw-key one-shot,
17 * so the key schedule and the GHASH table are built once per key rather than once per record.
18 * @ref Aes128GcmNs::block_init keys the single block the same way, off its own part of the same borrow,
19 * so a caller holding one direction's key material holds one pointer.
20 *
21 * The tag is detached: seal writes the ciphertext and the 16 tag bytes to separate destinations, which
22 * is where the SMB2 TRANSFORM_HEADER Signature field wants them. A wire format that carries the tag
23 * immediately after the ciphertext (QUIC, DTLS) passes @c ct_out + @c pt_len as @c tag_out and the tag
24 * lands in place.
25 *
26 * Host-tested against the NIST GCM vectors and RFC 9001 Appendix A.
27 *
28 * @author Douglas Quigg (dstroy0)
29 * @date 2026
30 */
31
32#ifndef PROTOCORE_AES128GCM_H
33#define PROTOCORE_AES128GCM_H
34
35#include "protocore_config.h" // the entry point: protocore_types.h for the widths
36
37#if PROTOCORE_ENABLE_AES128GCM
38
40
41/** @brief AEAD_AES_128_GCM key length in bytes. */
42#define PROTOCORE_AES128GCM_KEY_LEN 16
43
44/** @brief AEAD_AES_128_GCM nonce length in bytes. */
45#define PROTOCORE_AES128GCM_IV_LEN 12
46
47/** @brief AEAD_AES_128_GCM authentication tag length in bytes. */
48#define PROTOCORE_AES128GCM_TAG_LEN 16
49
50// PROTOCORE_AES128GCM_BORROW - the bytes a keyed context runs out of - is stated in protocore_config.h,
51// which sums it into the secure arena. A caller takes them once and passes the pointer to every call.
52
53/** @brief The key the AEAD context is bound to. */
54typedef struct
55{
56 const uint8_t *key; ///< PROTOCORE_AES128GCM_KEY_LEN bytes
57} Aes128GcmKeyArgs;
58
59/** @brief One record sealed under the bound key. */
60typedef struct
61{
62 const uint8_t *nonce; ///< PROTOCORE_AES128GCM_IV_LEN bytes
63 const uint8_t *aad; ///< additional authenticated data, NULL when @c aad_len is 0
64 size_t aad_len; ///< its length
65 const uint8_t *pt; ///< the plaintext
66 size_t pt_len; ///< its length
67 uint8_t *ct_out; ///< pt_len ciphertext bytes; may alias @c pt
68 uint8_t *tag_out; ///< PROTOCORE_AES128GCM_TAG_LEN bytes
69} Aes128GcmSealArgs;
70
71/** @brief One record opened under the bound key. */
72typedef struct
73{
74 const uint8_t *nonce; ///< PROTOCORE_AES128GCM_IV_LEN bytes
75 const uint8_t *aad; ///< additional authenticated data, NULL when @c aad_len is 0
76 size_t aad_len; ///< its length
77 const uint8_t *ct; ///< the ciphertext, tag not included
78 size_t ct_len; ///< its length
79 const uint8_t *tag; ///< PROTOCORE_AES128GCM_TAG_LEN bytes to verify against
80 uint8_t *out; ///< ct_len plaintext bytes; may alias @c ct
81} Aes128GcmOpenArgs;
82
83/** @brief The key the single-block cipher is bound to. */
84typedef struct
85{
86 const uint8_t *key; ///< PROTOCORE_AES128GCM_KEY_LEN bytes
87} Aes128GcmBlockKeyArgs;
88
89/** @brief The one block an ECB encryption runs over. */
90typedef struct
91{
92 const uint8_t *in; ///< 16 input bytes
93 uint8_t *out; ///< 16 output bytes; may alias @c in
94} Aes128GcmBlockArgs;
95
96/**
97 * @brief AEAD_AES_128_GCM (RFC 5116, NIST SP 800-38D) and the AES-128 block.
98 *
99 * A caller sets the members a call takes, invokes it through ::Aes128Gcm with the bytes it runs out of,
100 * and reads the outcome off the same handle. How those bytes are carved is this module's and is never
101 * named here.
102 *
103 * Aes128Gcm.key_args.key = key;
104 * Aes128Gcm.key_init(work);
105 * Aes128Gcm.seal_args.nonce = nonce;
106 * Aes128Gcm.seal_args.aad = hdr;
107 * Aes128Gcm.seal_args.aad_len = hdr_len;
108 * Aes128Gcm.seal_args.pt = pt;
109 * Aes128Gcm.seal_args.pt_len = pt_len;
110 * Aes128Gcm.seal_args.ct_out = ct;
111 * Aes128Gcm.seal_args.tag_out = ct + pt_len;
112 * Aes128Gcm.seal(work);
113 *
114 * @var Aes128GcmNs::key_args the key the AEAD context is bound to
115 * @var Aes128GcmNs::seal_args one record sealed under the bound key
116 * @var Aes128GcmNs::open_args one record opened under the bound key
117 * @var Aes128GcmNs::block_key_args the key the single-block cipher is bound to
118 * @var Aes128GcmNs::block_args the one block an ECB encryption runs over
119 * @var Aes128GcmNs::ok a call's true/false outcome
120 * @var Aes128GcmNs::key_init bind the borrow as a context keyed with @ref Aes128GcmNs::key_args
121 * @var Aes128GcmNs::key_wipe release what the AEAD context attached; call on rekey and on close
122 * @var Aes128GcmNs::seal encrypt one record and write its detached tag
123 * @var Aes128GcmNs::open verify the tag over aad || ct in constant time, then decrypt
124 * @var Aes128GcmNs::block_init key the single block with @ref Aes128GcmNs::block_key_args
125 * @var Aes128GcmNs::block_encrypt encrypt one 16-byte block (ECB) under that key
126 * @var Aes128GcmNs::block_wipe release what the block context attached; call on rekey and on close
127 *
128 * @ref Aes128GcmNs::open produces no plaintext on a tag mismatch: it authenticates the received
129 * ciphertext first and leaves @c out untouched when @ref Aes128GcmNs::ok comes back false.
130 *
131 * The AEAD key and the block key are separate: @ref Aes128GcmNs::key_init and
132 * @ref Aes128GcmNs::block_init bind different parts of the borrow, so a record and its header mask run
133 * under the two keys the protocol derived without either call disturbing the other.
134 *
135 * @c work is PROTOCORE_AES128GCM_BORROW secure bytes the CALLER took, at an address it knows. It is not held past the
136 * call, so nothing here aliases it. The caller releases it, and the pool wipes on release; this module neither takes
137 * it, holds it, releases it, nor wipes it. The borrow IS the keyed context, so two directions are two borrows and never
138 * collide, and both block contexts die with the release.
139 *
140 * No storage member and no context: a caller sets operands and reads @ref Aes128GcmNs::ok, and that is
141 * all the surface there is.
142 */
143typedef struct
144{
145 Aes128GcmKeyArgs key_args;
146 Aes128GcmSealArgs seal_args;
147 Aes128GcmOpenArgs open_args;
148 Aes128GcmBlockKeyArgs block_key_args;
149 Aes128GcmBlockArgs block_args;
150 proto_bool ok;
151} Aes128GcmVars;
152
153/** @brief The operands and the outcome. */
154extern Aes128GcmVars Aes128GcmV;
155
156/** @brief The entries. */
157typedef struct
158{
159 void (*const key_init)(uint8_t *work);
160 void (*const key_wipe)(uint8_t *work);
161 void (*const seal)(uint8_t *work);
162 void (*const open)(uint8_t *work);
163 void (*const block_init)(uint8_t *work);
164 void (*const block_encrypt)(uint8_t *work);
165 void (*const block_wipe)(uint8_t *work);
166} Aes128GcmNs;
167
168// What the table binds, defined once in the .c and taking one parameter each: everything
169// else an entry needs is an operand in Aes128GcmV or a region of the borrow at a fixed offset.
170void protocore_aes128_gcm_key_init(uint8_t *work);
171void protocore_aes128_gcm_key_wipe(uint8_t *work);
172void protocore_aes128_gcm_seal(uint8_t *work);
173void protocore_aes128_gcm_open(uint8_t *work);
174void protocore_aes128_gcm_block_init(uint8_t *work);
175void protocore_aes128_gcm_block_encrypt(uint8_t *work);
176void protocore_aes128_gcm_block_wipe(uint8_t *work);
177
178// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
179// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
180// `Aes128Gcm.key_init(work)` resolves to a named function and becomes a DIRECT call. An extern table
181// leaves the call indirect and the symbol live at every level, -O2 -flto included.
182static const Aes128GcmNs Aes128Gcm __attribute__((unused)) = {
183 .key_init = protocore_aes128_gcm_key_init,
184 .key_wipe = protocore_aes128_gcm_key_wipe,
185 .seal = protocore_aes128_gcm_seal,
186 .open = protocore_aes128_gcm_open,
187 .block_init = protocore_aes128_gcm_block_init,
188 .block_encrypt = protocore_aes128_gcm_block_encrypt,
189 .block_wipe = protocore_aes128_gcm_block_wipe,
190};
191
193
194#endif // PROTOCORE_ENABLE_AES128GCM
195
196#endif // PROTOCORE_AES128GCM_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97