ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
aesgcm.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 aesgcm.h
6 * @brief AES-256-GCM AEAD (RFC 5116) - keyed, detached tag.
7 *
8 * The shared AES-256-GCM primitive for the whole library (SSH aes256-gcm@openssh.com per RFC 5647, and
9 * SMB 3.x transport encryption). The entries below are one surface over both arms: the GCM construction
10 * and the table GHASH run in software on both, and only the AES-256 block under them changes arm - the
11 * part's AES accelerator where it carries one, software AES-256 where it does not.
12 *
13 * The entries are keyed: @ref AesGcmNs::key_init once from the 32-byte key, then @ref AesGcmNs::seal or
14 * @ref AesGcmNs::open per record against it. There is no raw-key one-shot; key_init binds the key,
15 * derives H = E(K, 0^128) and builds the 4-bit GHASH table, a per-key cost every record would otherwise
16 * repeat.
17 *
18 * The tag is detached: seal writes the ciphertext and the 16 tag bytes to separate destinations, which
19 * is where the SSH packet and the SMB2 TRANSFORM_HEADER Signature field each want them. No nonce state
20 * is kept or advanced by a seal or an open; @ref AesGcmNs::iv_increment advances the caller's.
21 *
22 * Host-tested byte-exact against the NIST/McGrew AES-256-GCM vectors.
23 *
24 * @author Douglas Quigg (dstroy0)
25 * @date 2026
26 */
27
28#ifndef PROTOCORE_AESGCM_H
29#define PROTOCORE_AESGCM_H
30
31#include "protocore_config.h" // the entry point: protocore_types.h for the widths
32
33#if PROTOCORE_ENABLE_AESGCM
34
36
37/** @brief AES-256-GCM key length in bytes. */
38#define PROTOCORE_AESGCM_KEY_LEN 32
39
40/** @brief GCM nonce length in bytes = fixed_field(4) || invocation_counter(8). */
41#define PROTOCORE_AESGCM_IV_LEN 12
42
43/** @brief GCM authentication tag length in bytes. */
44#define PROTOCORE_AESGCM_TAG_LEN 16
45
46// PROTOCORE_AESGCM_BORROW - the bytes a keyed context runs out of - is stated in protocore_config.h,
47// which sums it into the secure arena. A caller takes them once and passes the pointer to every call.
48
49/** @brief The key a context is bound to. */
50typedef struct
51{
52 const uint8_t *key; ///< PROTOCORE_AESGCM_KEY_LEN bytes
53} AesGcmKeyArgs;
54
55/** @brief One record sealed under the bound key. */
56typedef struct
57{
58 const uint8_t *nonce; ///< PROTOCORE_AESGCM_IV_LEN bytes
59 const uint8_t *aad; ///< additional authenticated data, NULL when @c aad_len is 0
60 size_t aad_len; ///< its length
61 const uint8_t *pt; ///< the plaintext
62 size_t pt_len; ///< its length
63 uint8_t *ct_out; ///< pt_len ciphertext bytes; may alias @c pt
64 uint8_t *tag_out; ///< PROTOCORE_AESGCM_TAG_LEN bytes
65} AesGcmSealArgs;
66
67/** @brief One record opened under the bound key. */
68typedef struct
69{
70 const uint8_t *nonce; ///< PROTOCORE_AESGCM_IV_LEN bytes
71 const uint8_t *aad; ///< additional authenticated data, NULL when @c aad_len is 0
72 size_t aad_len; ///< its length
73 const uint8_t *ct; ///< the ciphertext
74 size_t ct_len; ///< its length
75 const uint8_t *tag; ///< PROTOCORE_AESGCM_TAG_LEN bytes to verify against
76 uint8_t *out; ///< ct_len plaintext bytes; may alias @c ct
77} AesGcmOpenArgs;
78
79/** @brief The nonce an invocation counter is advanced in. */
80typedef struct
81{
82 uint8_t *iv; ///< PROTOCORE_AESGCM_IV_LEN bytes, advanced in place
83} AesGcmIvArgs;
84
85/**
86 * @brief AES-256-GCM (RFC 5116, NIST SP 800-38D).
87 *
88 * A caller sets the members a call takes, invokes it through ::AesGcm with the bytes it runs out of,
89 * and reads the outcome off the same handle. How those bytes are carved is this module's and is never
90 * named here.
91 *
92 * AesGcm.key_args.key = key;
93 * AesGcm.key_init(work);
94 * AesGcm.seal_args.nonce = iv;
95 * AesGcm.seal_args.aad = aad;
96 * AesGcm.seal_args.aad_len = aad_len;
97 * AesGcm.seal_args.pt = pt;
98 * AesGcm.seal_args.pt_len = pt_len;
99 * AesGcm.seal_args.ct_out = ct;
100 * AesGcm.seal_args.tag_out = tag;
101 * AesGcm.seal(work);
102 * AesGcm.iv_args.iv = iv;
103 * AesGcm.iv_increment(work);
104 *
105 * @var AesGcmNs::key_args the key a context is bound to
106 * @var AesGcmNs::seal_args one record sealed under the bound key
107 * @var AesGcmNs::open_args one record opened under the bound key
108 * @var AesGcmNs::iv_args the nonce an invocation counter is advanced in
109 * @var AesGcmNs::ok a call's true/false outcome
110 * @var AesGcmNs::key_init bind the borrow as a context keyed with @ref AesGcmNs::key_args
111 * @var AesGcmNs::key_wipe release what the context attached; call on rekey and on close
112 * @var AesGcmNs::seal encrypt one record and write its detached tag
113 * @var AesGcmNs::open verify the tag over aad || ct in constant time, then decrypt
114 * @var AesGcmNs::iv_increment advance the RFC 5647 invocation counter, the nonce's low 8 bytes as a
115 * big-endian integer; the 4-byte fixed field never changes
116 *
117 * @ref AesGcmNs::open produces no plaintext on a tag mismatch: it authenticates the received ciphertext
118 * first and leaves @c out untouched when @ref AesGcmNs::ok comes back false.
119 *
120 * @c work is PROTOCORE_AESGCM_BORROW secure bytes the CALLER took, at an address it knows. It is not held past the
121 * call, so nothing here aliases it. The caller releases it, and the pool wipes on release; this module neither takes
122 * it, holds it, releases it, nor wipes it. The borrow IS the keyed context, so two connections are two borrows and
123 * never collide, and the key material dies with the release. @ref AesGcmNs::iv_increment works on the caller's nonce
124 * and reads nothing out of the borrow.
125 *
126 * No storage member and no context: a caller sets operands and reads @ref AesGcmNs::ok, and that is
127 * all the surface there is.
128 */
129typedef struct
130{
131 AesGcmKeyArgs key_args;
132 AesGcmSealArgs seal_args;
133 AesGcmOpenArgs open_args;
134 AesGcmIvArgs iv_args;
135 proto_bool ok;
136} AesGcmVars;
137
138/** @brief The operands and the outcome. */
139extern AesGcmVars AesGcmV;
140
141/** @brief The entries. */
142typedef struct
143{
144 void (*const key_init)(uint8_t *work);
145 void (*const key_wipe)(uint8_t *work);
146 void (*const seal)(uint8_t *work);
147 void (*const open)(uint8_t *work);
148 void (*const iv_increment)(uint8_t *work);
149} AesGcmNs;
150
151// What the table binds, defined once in the .c and taking one parameter each: everything
152// else an entry needs is an operand in AesGcmV or a region of the borrow at a fixed offset.
153void protocore_aes_gcm_key_init(uint8_t *work);
154void protocore_aes_gcm_key_wipe(uint8_t *work);
155void protocore_aes_gcm_seal(uint8_t *work);
156void protocore_aes_gcm_open(uint8_t *work);
157void protocore_aes_gcm_iv_increment(uint8_t *work);
158
159// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
160// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
161// `AesGcm.key_init(work)` resolves to a named function and becomes a DIRECT call. An extern table
162// leaves the call indirect and the symbol live at every level, -O2 -flto included.
163static const AesGcmNs AesGcm __attribute__((unused)) = {
164 .key_init = protocore_aes_gcm_key_init,
165 .key_wipe = protocore_aes_gcm_key_wipe,
166 .seal = protocore_aes_gcm_seal,
167 .open = protocore_aes_gcm_open,
168 .iv_increment = protocore_aes_gcm_iv_increment,
169};
170
172
173#endif // PROTOCORE_ENABLE_AESGCM
174
175#endif // PROTOCORE_AESGCM_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