ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
aesgcm.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 aesgcm.h
6 * @brief AES-256-GCM AEAD (RFC 5116) - stateless, detached-tag API.
7 *
8 * A key is a per-connection object, so this is a KEYED api: build a context once from the 32-byte key
9 * with pc_aesgcm_key_init(), then seal or open each record against it. There is deliberately no
10 * raw-key one-shot. Standing a cipher context up and tearing it down costs ~9,200 cycles on an
11 * ESP32-S3 once the AES peripheral has actually been used - a FIXED cost per call, so roughly 10%% of a
12 * 1 KiB record, 30%% of a 256 B TLS record, and most of a small interactive SSH packet. A one-shot
13 * entry point would have let every caller pay that without seeing it.
14 *
15 * The context holds an expanded key schedule for as long as the caller holds it, so it belongs in the
16 * caller's secure storage and must be wiped on rekey and on close. This is not a new exposure: the raw
17 * key is already resident for exactly that long, and the schedule is derivable from it.
18 *
19 * Used by SSH aes256-gcm@openssh.com (RFC 5647: advance the invocation counter with
20 * pc_aesgcm_iv_increment after every packet) and SMB 3.x transport encryption.
21 *
22 * The implementation is a backend under board_drivers/ selected by the vendor's PC_HAS_HW_AESGCM.
23 * Host-tested byte-exact against the NIST/McGrew AES-256-GCM vectors.
24 *
25 * @author Douglas Quigg (dstroy0)
26 * @date 2026
27 */
28
29#ifndef PROTOCORE_AESGCM_H
30#define PROTOCORE_AESGCM_H
31
32#include "protocore_config.h" // PC_WORK_AESGCM sizes a context
33#include "shared_primitives/span.h" // pc_cspan: what the seal produced (empty == it did not)
34#include <stddef.h>
35#include <stdint.h>
36
37/** @brief AES-256-GCM key length (bytes). */
38#define PC_AESGCM_KEY_LEN 32
39/** @brief GCM nonce length (bytes) = fixed_field(4) || invocation_counter(8). */
40#define PC_AESGCM_IV_LEN 12
41/** @brief GCM authentication tag length (bytes). */
42#define PC_AESGCM_TAG_LEN 16
43
44/**
45 * @brief Opaque keyed AES-256-GCM context. Forward-declared only: the definition is the backend's, so
46 * consumers hold it by pointer and size its storage with PC_WORK_AESGCM.
47 */
48struct pc_aesgcm_key;
49
50/**
51 * @brief Bind @p storage as a context keyed with @p key.
52 *
53 * @param storage exactly PC_WORK_AESGCM bytes of the caller's secure storage, 8-aligned. Declare it as
54 * that macro and it cannot be wrong - the backend static_asserts its context fits, so
55 * the size is settled at compile time and there is nothing to check here. Must outlive
56 * every seal/open against it, and must be wiped on rekey and on close.
57 * @return the context, or nullptr if the vendor rejected the key.
58 */
59pc_aesgcm_key *pc_aesgcm_key_init(void *storage, const uint8_t key[PC_AESGCM_KEY_LEN]);
60
61/** @brief Wipe the expanded schedule. Call on rekey and on close; the storage stays the caller's. */
62void pc_aesgcm_key_wipe(pc_aesgcm_key *k);
63
64/**
65 * @brief Seal one record under @p k and @p nonce.
66 *
67 * @p ct_out receives @p pt_len ciphertext bytes (may alias @p pt) and @p tag_out the 16-byte tag. No
68 * state is kept or advanced - the caller owns the nonce.
69 */
70pc_cspan pc_aesgcm_seal(pc_aesgcm_key *k, const uint8_t nonce[PC_AESGCM_IV_LEN], const uint8_t *aad, size_t aad_len,
71 const uint8_t *pt, size_t pt_len, uint8_t *ct_out, uint8_t tag_out[PC_AESGCM_TAG_LEN]);
72
73/**
74 * @brief Open one record: verify @p tag over @p aad || @p ct in constant time, then (only on success)
75 * decrypt @p ct into @p out (may alias @p ct). @return true iff the tag is valid.
76 */
77bool pc_aesgcm_open(pc_aesgcm_key *k, const uint8_t nonce[PC_AESGCM_IV_LEN], const uint8_t *aad, size_t aad_len,
78 const uint8_t *ct, size_t ct_len, const uint8_t tag[PC_AESGCM_TAG_LEN], uint8_t *out);
79
80/**
81 * @brief Advance the RFC 5647 invocation counter: the low 8 bytes of the 12-byte nonce as a big-endian
82 * integer; the 4-byte fixed field never changes. SSH calls this after each sealed/opened packet.
83 */
85
86#endif // PROTOCORE_AESGCM_H
bool pc_aesgcm_open(pc_aesgcm_key *k, const uint8_t nonce[PC_AESGCM_IV_LEN], const uint8_t *aad, size_t aad_len, const uint8_t *ct, size_t ct_len, const uint8_t tag[PC_AESGCM_TAG_LEN], uint8_t *out)
Open one record: verify tag over aad || ct in constant time, then (only on success) decrypt ct into o...
pc_aesgcm_key * pc_aesgcm_key_init(void *storage, const uint8_t key[PC_AESGCM_KEY_LEN])
Bind storage as a context keyed with key.
pc_cspan pc_aesgcm_seal(pc_aesgcm_key *k, const uint8_t nonce[PC_AESGCM_IV_LEN], const uint8_t *aad, size_t aad_len, const uint8_t *pt, size_t pt_len, uint8_t *ct_out, uint8_t tag_out[PC_AESGCM_TAG_LEN])
Seal one record under k and nonce.
#define PC_AESGCM_TAG_LEN
GCM authentication tag length (bytes).
Definition aesgcm.h:42
void pc_aesgcm_key_wipe(pc_aesgcm_key *k)
Wipe the expanded schedule. Call on rekey and on close; the storage stays the caller's.
#define PC_AESGCM_IV_LEN
GCM nonce length (bytes) = fixed_field(4) || invocation_counter(8).
Definition aesgcm.h:40
void pc_aesgcm_iv_increment(uint8_t iv[PC_AESGCM_IV_LEN])
Advance the RFC 5647 invocation counter: the low 8 bytes of the 12-byte nonce as a big-endian integer...
Definition aesgcm.cpp:30
#define PC_AESGCM_KEY_LEN
AES-256-GCM key length (bytes).
Definition aesgcm.h:38
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