ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
aes128gcm.h
Go to the documentation of this file.
1// 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 AES-128 block cipher + AEAD_AES_128_GCM (RFC 5116 / NIST SP 800-38D).
7 *
8 * The generic 128-bit AES primitives: encrypt one 16-byte block under a 128-bit key (ECB - used for
9 * GCM's counter mode and for keystream sampling), and the one-shot AEAD_AES_128_GCM seal/open (96-bit
10 * nonce, 128-bit tag). Consumed by QUIC Initial packet protection (RFC 9001 sec 5.3/5.4), the DTLS 1.3
11 * record layer, and SMB 3.x transport encryption - a single home for the primitive, not per-protocol copies.
12 *
13 * On Arduino (ESP32) the AES block is mbedtls, routed to the hardware AES accelerator; on native host
14 * builds a compact software AES-128 is used so the whole AEAD is unit-testable off-target. GHASH and the
15 * counter loop are the same software on both. Pure, zero heap, host-tested against the NIST GCM vectors
16 * and RFC 9001 Appendix A.
17 *
18 * @author Douglas Quigg (dstroy0)
19 * @date 2026
20 */
21
22#ifndef PROTOCORE_AES128GCM_H
23#define PROTOCORE_AES128GCM_H
24
25#include "protocore_config.h"
26
27// Shared by the HTTP/3 (QUIC) packet protection and the DTLS 1.3 record layer.
28#if (PC_ENABLE_HTTP3 || PC_ENABLE_DTLS || PC_ENABLE_SMB)
29
30#include "shared_primitives/span.h" // pc_cspan: what the seal produced (empty == it did not)
31#include <stddef.h>
32#include <stdint.h>
33
34/** @brief AEAD_AES_128_GCM key length in bytes. */
35#define PC_AES128GCM_KEY_LEN 16
36/** @brief AEAD_AES_128_GCM nonce length in bytes. */
37#define PC_AES128GCM_IV_LEN 12
38/** @brief AEAD_AES_128_GCM authentication tag length in bytes. */
39#define PC_AES128GCM_TAG_LEN 16
40
41// ---------------------------------------------------------------------------
42// AES-128 single-block primitive (used by GCM and by header protection)
43// ---------------------------------------------------------------------------
44
45// Opaque: the definition is private to the backend under board_drivers/ that this build selected -
46// consumers know only the symbol and hold it via pc_aes128*. No vendor type is named here.
47struct pc_aes128;
48
49/**
50 * @brief Storage this module wants for one AES-128 context.
51 *
52 * The type is opaque, so a consumer cannot size it - this module owns the definition and therefore
53 * owns the allocation. Call inside a SecureScope: the scope is how the caller states how long it
54 * needs the resource, and the pool wipes the key schedule when that scope ends.
55 *
56 * @return a context to pass to pc_aes128_init(), or nullptr if the pool could not satisfy it.
57 */
58pc_aes128 *pc_aes128_wants(void);
59
60/** @brief Load a 128-bit key and expand the encryption key schedule. */
61void pc_aes128_init(pc_aes128 *ctx, const uint8_t key[16]);
62
63/** @brief Encrypt one 16-byte block (ECB). @p in and @p out may alias. */
64void pc_aes128_encrypt_block(pc_aes128 *ctx, const uint8_t in[16], uint8_t out[16]);
65
66/** @brief Wipe the key schedule (and release mbedtls state on Arduino). */
67void pc_aes128_wipe(pc_aes128 *ctx);
68
69// ---------------------------------------------------------------------------
70// AEAD_AES_128_GCM (96-bit nonce, 128-bit tag) - keyed
71// ---------------------------------------------------------------------------
72
73/**
74 * @brief Opaque keyed AEAD_AES_128_GCM context. Forward-declared only: the definition belongs to the
75 * backend, so consumers hold it by pointer and size its storage with PC_WORK_AES128GCM.
76 *
77 * This api is keyed, and there is deliberately no raw-key one-shot. A key protects a whole connection's
78 * worth of records, but building a cipher context and tearing it down costs ~9,200 cycles on an
79 * ESP32-S3 once the AES peripheral has been used - a FIXED cost per record, which is most of the work
80 * for the small records QUIC and DTLS actually send. A one-shot entry point would let every caller pay
81 * that without seeing it.
82 *
83 * The context holds an expanded key schedule for as long as the caller holds it, so it belongs in the
84 * caller's secure storage and must be wiped on rekey and on close.
85 */
86struct pc_aes128gcm_key;
87
88/**
89 * @brief Bind @p storage as a context keyed with @p key.
90 *
91 * @param storage secure, PC_WORK_AES128GCM bytes, 8-aligned. Declare it as that macro and it cannot be
92 * wrong - the backend static_asserts its context fits, so the size is settled at compile
93 * time and there is nothing to check here.
94 * @return the context, or nullptr if the vendor rejected the key.
95 */
96pc_aes128gcm_key *pc_aes128gcm_key_init(void *storage, const uint8_t key[PC_AES128GCM_KEY_LEN]);
97
98/** @brief Wipe the expanded schedule. Call on rekey and on close; the storage stays the caller's. */
99void pc_aes128gcm_key_wipe(pc_aes128gcm_key *k);
100
101/**
102 * @brief Seal one record: encrypt @p pt and authenticate it together with @p aad.
103 *
104 * @p ct_out receives @p pt_len ciphertext bytes (may alias @p pt) and @p tag_out the 16-byte tag.
105 *
106 * The tag is always detached. A wire format that carries the tag immediately after the ciphertext (QUIC,
107 * DTLS) is not a different operation - pass `ct_out + pt_len` as @p tag_out and it is written in place.
108 * That is why there is no second "attached" pair of entry points.
109 */
110pc_cspan pc_aes128gcm_seal(pc_aes128gcm_key *k, const uint8_t nonce[PC_AES128GCM_IV_LEN], const uint8_t *aad,
111 size_t aad_len, const uint8_t *pt, size_t pt_len, uint8_t *ct_out,
112 uint8_t tag_out[PC_AES128GCM_TAG_LEN]);
113
114/**
115 * @brief Open one record: verify @p tag over @p aad || @p ct in constant time, then (only on success)
116 * decrypt @p ct into @p out (may alias @p ct).
117 *
118 * @p ct_len is the ciphertext length, NOT including the tag.
119 * @return true iff the tag is valid; on mismatch nothing is written.
120 */
121bool pc_aes128gcm_open(pc_aes128gcm_key *k, const uint8_t nonce[PC_AES128GCM_IV_LEN], const uint8_t *aad,
122 size_t aad_len, const uint8_t *ct, size_t ct_len, const uint8_t tag[PC_AES128GCM_TAG_LEN],
123 uint8_t *out);
124
125#endif // PC_ENABLE_HTTP3 || PC_ENABLE_DTLS
126
127#endif // PROTOCORE_AES128GCM_H
User-facing configuration for ProtoCore.
A byte region whose run length is bound in both directions.
A read-only byte region.
Definition span.h:79