ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
esp_aesgcm.cpp
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 esp_aesgcm.cpp
6 * @brief AES-256-GCM on Espressif silicon, through the vendor's own AEAD.
7 *
8 * Hands the whole AEAD to mbedtls rather than driving the block cipher and folding GHASH in software.
9 * That is not a style preference - measured on an ESP32-S3 at 240 MHz, sealing 1 KiB:
10 *
11 * this path (vendor GCM) 81,085 cycles 3.0 MB/s
12 * manual HW-AES block + software GHASH 616,567 cycles 0.4 MB/s
13 *
14 * 7.6x, same chip, same data. The old code took the manual path whenever SOC_AES_SUPPORT_GCM was
15 * unset, i.e. whenever we concluded the die had no GCM mode - and then hand-rolled a slower
16 * replacement. The vendor's implementation knows what its own silicon can do; deciding that is its
17 * job, not the core's.
18 *
19 * Vendor headers are fine here: this is board_drivers, the partition vendor code is segregated to.
20 */
21
23#include "crypto/aead/aesgcm.h"
24#include "crypto/crypto_opt.h"
25#include "server/mmgr/secure.h"
26#include <string.h>
27
28#if PC_HAS_HW_AESGCM
29
30#include <mbedtls/gcm.h>
31
33
34// ===========================================================================
35// Hardware GCM path (mbedtls_gcm -> the ESP32 AES peripheral).
36// ===========================================================================
37
38// The context is the vendor's, held for the life of the key. Standing one up and tearing it down
39// costs ~9,200 cycles once the AES peripheral has been used - measured, and the whole reason this
40// api is keyed rather than one-shot. The size assert is what keeps PC_WORK_AESGCM honest against a
41// vendor header we do not control.
42static_assert(sizeof(mbedtls_gcm_context) <= PC_WORK_AESGCM,
43 "mbedtls_gcm_context outgrew PC_WORK_AESGCM - raise it in protocore_config.h, which derives "
44 "PC_SECURE_ARENA_SIZE from it");
45
46pc_aesgcm_key *pc_aesgcm_key_init(void *storage, const uint8_t key[PC_AESGCM_KEY_LEN])
47{
48 mbedtls_gcm_context *g = reinterpret_cast<mbedtls_gcm_context *>(storage);
49 mbedtls_gcm_init(g);
50 if (mbedtls_gcm_setkey(g, MBEDTLS_CIPHER_ID_AES, key, 256) != 0)
51 {
52 mbedtls_gcm_free(g);
53 return nullptr;
54 }
55 return reinterpret_cast<pc_aesgcm_key *>(g);
56}
57
58void pc_aesgcm_key_wipe(pc_aesgcm_key *k)
59{
60 mbedtls_gcm_context *g = reinterpret_cast<mbedtls_gcm_context *>(k);
61 mbedtls_gcm_free(g); // releases whatever the vendor attached
62 pc_secure_wipe(reinterpret_cast<uint8_t *>(g), sizeof(mbedtls_gcm_context));
63}
64
65pc_cspan pc_aesgcm_seal(pc_aesgcm_key *k, const uint8_t nonce[PC_AESGCM_IV_LEN], const uint8_t *aad, size_t aad_len,
66 const uint8_t *pt, size_t pt_len, uint8_t *ct_out, uint8_t tag_out[PC_AESGCM_TAG_LEN])
67{
68 mbedtls_gcm_context *g = reinterpret_cast<mbedtls_gcm_context *>(k);
69 if (mbedtls_gcm_crypt_and_tag(g, MBEDTLS_GCM_ENCRYPT, pt_len, nonce, PC_AESGCM_IV_LEN, aad, aad_len, pt, ct_out,
70 PC_AESGCM_TAG_LEN, tag_out) != 0)
71 {
72 return {};
73 }
74 return pc_cspan_from(ct_out, pt_len); // the tag rides in tag_out, not in this span
75}
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 mbedtls_gcm_context *g = reinterpret_cast<mbedtls_gcm_context *>(k);
81 return mbedtls_gcm_auth_decrypt(g, ct_len, nonce, PC_AESGCM_IV_LEN, aad, aad_len, tag, PC_AESGCM_TAG_LEN, ct,
82 out) == 0;
83}
84
85#endif // PC_HAS_HW_AESGCM
AES-256-GCM AEAD (RFC 5116) - stateless, detached-tag API.
#define PC_AESGCM_TAG_LEN
GCM authentication tag length (bytes).
Definition aesgcm.h:42
#define PC_AESGCM_IV_LEN
GCM nonce length (bytes) = fixed_field(4) || invocation_counter(8).
Definition aesgcm.h:40
#define PC_AESGCM_KEY_LEN
AES-256-GCM key length (bytes).
Definition aesgcm.h:38
Per-translation-unit optimization override for hot, pure-integer crypto.
The one vendor/die selector for the whole library.
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.
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_WORK_AESGCM
Secure pool accessor - borrows that hold key material.
pc_cspan pc_cspan_from(const uint8_t *p, size_t len)
Bind a read-only span to memory whose extent is only known at run time.
Definition span.h:103
A read-only byte region.
Definition span.h:79