ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
aes256ctr.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 aes256ctr.cpp
6 * @brief AES-256-CTR implementation (stateless, see aes256ctr.h).
7 *
8 * The ephemeral key schedule and keystream block are borrowed from the shared crypto scratch
9 * from the secure pool - never a stack or BSS local - and wiped on release, so expanded key material is
10 * funneled through the one hardened, single-op region rather than scattered across the address space.
11 *
12 * Arduino path: mbedtls_aes_crypt_ctr(), which the ESP32 mbedtls port routes to the hardware AES
13 * accelerator. Native path: a compact software AES-256 (256-byte forward S-box + GF(2^8) MixColumns),
14 * for host-side unit tests only.
15 */
16
18#include "crypto/crypto_opt.h"
19#include "server/mmgr/secure.h"
20#include <string.h>
21#ifdef ARDUINO
22#include <mbedtls/aes.h>
23#else
24#include "crypto/cipher/aes_block.h" // native software AES S-box/blocks (ARDUINO uses the mbedtls block above)
25#endif
27
28// ============================================================================
29// ARDUINO - hardware-accelerated path via mbedtls
30// ============================================================================
31
32#ifdef ARDUINO
33
34// The whole working set in one borrow: expanded key schedule + one keystream block. Its size is a
35// per-vendor constant that belongs in board_drivers/ (see the handover) - stated here for now.
37{
38 mbedtls_aes_context aes;
39 uint8_t ks[16];
40};
41static_assert(sizeof(Aes256CtrWork) <= PC_WORK_AES256CTR,
42 "Aes256CtrWork outgrew PC_WORK_AES256CTR - raise it in protocore_config.h, which derives "
43 "PC_SECURE_ARENA_SIZE from it");
44
45void pc_aes256ctr_crypt(const uint8_t key[PC_AES256CTR_KEY_LEN], uint8_t counter[PC_AES256CTR_CTR_LEN],
46 const uint8_t *in, uint8_t *out, size_t len)
47{
48 // Schedule + keystream block are borrowed, not local; the pool wipes them on release.
49 SecureBorrow ws_b(sizeof(Aes256CtrWork), alignof(Aes256CtrWork));
50 const pc_span &ws = ws_b.span();
51 if (!pc_span_ok(ws))
52 {
53 return;
54 }
55 Aes256CtrWork *w = reinterpret_cast<Aes256CtrWork *>(ws.buf);
56 mbedtls_aes_context *aes = &w->aes;
57 uint8_t *ks = w->ks;
58 mbedtls_aes_init(aes);
59 mbedtls_aes_setkey_enc(aes, key, 256);
60 size_t nc_off = 0; // block-aligned callers (SSH) leave this 0; the counter alone carries the stream state
61 mbedtls_aes_crypt_ctr(aes, len, &nc_off, counter, ks, in, out);
62 mbedtls_aes_free(aes);
63}
64
65// ============================================================================
66// NATIVE - software AES-256 (for host-side unit tests only)
67// ============================================================================
68
69#else
70
71// The whole working set in one borrow: 60-word round-key schedule + one keystream block.
72struct Aes256CtrWork
73{
74 uint32_t rk[60];
75 uint8_t ks[16];
76};
77
78void pc_aes256ctr_crypt(const uint8_t key[PC_AES256CTR_KEY_LEN], uint8_t counter[PC_AES256CTR_CTR_LEN],
79 const uint8_t *in, uint8_t *out, size_t len)
80{
81 // Round-key schedule and the keystream block are borrowed; the pool wipes them on release.
82 SecureBorrow ws_b(sizeof(Aes256CtrWork), alignof(Aes256CtrWork));
83 const pc_span &ws = ws_b.span();
84 if (!pc_span_ok(ws))
85 {
86 return;
87 }
88 Aes256CtrWork *w = reinterpret_cast<Aes256CtrWork *>(ws.buf);
89 uint32_t *rk = w->rk;
90 uint8_t *ks = w->ks;
91 pc_aes_key_expand(key, 8, rk);
92 uint8_t pos = 0;
93 for (size_t i = 0; i < len; i++)
94 {
95 if (pos == 0)
96 {
97 pc_aes_encrypt_block(rk, 14, counter, ks); // keystream = AES(counter)
98 for (int j = 15; j >= 0; j--) // then advance the big-endian counter by one block
99 {
100 if (++counter[j])
101 {
102 break;
103 }
104 }
105 }
106 out[i] = in[i] ^ ks[pos];
107 pos = (uint8_t)((pos + 1u) & 0x0fu);
108 }
109}
110
111#endif // ARDUINO
112
113// ---------------------------------------------------------------------------
114// Length peek (used by the SSH recv path; mirrors pc_chachapoly_get_length)
115// ---------------------------------------------------------------------------
116
117uint32_t pc_aes256ctr_get_length(const uint8_t key[PC_AES256CTR_KEY_LEN], const uint8_t counter[PC_AES256CTR_CTR_LEN],
118 const uint8_t enc4[4])
119{
120 // Produce the keystream block for @p counter (AES-ECB) in the shared crypto scratch, then XOR the first
121 // 4 bytes to recover the length. @p counter is not advanced and no cipher state touches the stack.
122 SecureBorrow ws_b(sizeof(Aes256CtrWork), alignof(Aes256CtrWork));
123 const pc_span &ws = ws_b.span();
124 if (!pc_span_ok(ws))
125 {
126 return 0;
127 }
128 Aes256CtrWork *w = reinterpret_cast<Aes256CtrWork *>(ws.buf);
129 uint8_t *ks = w->ks;
130#ifdef ARDUINO
131 mbedtls_aes_init(&w->aes);
132 mbedtls_aes_setkey_enc(&w->aes, key, 256);
133 mbedtls_aes_crypt_ecb(&w->aes, MBEDTLS_AES_ENCRYPT, counter, ks);
134 mbedtls_aes_free(&w->aes);
135#else
136 pc_aes_key_expand(key, 8, w->rk);
137 pc_aes_encrypt_block(w->rk, 14, counter, ks);
138#endif
139 uint32_t len = ((uint32_t)(enc4[0] ^ ks[0]) << 24) | ((uint32_t)(enc4[1] ^ ks[1]) << 16) |
140 ((uint32_t)(enc4[2] ^ ks[2]) << 8) | (uint32_t)(enc4[3] ^ ks[3]);
141 return len;
142}
void pc_aes256ctr_crypt(const uint8_t key[PC_AES256CTR_KEY_LEN], uint8_t counter[PC_AES256CTR_CTR_LEN], const uint8_t *in, uint8_t *out, size_t len)
Encrypt or decrypt len bytes with AES-256-CTR (the two are identical).
Definition aes256ctr.cpp:45
uint32_t pc_aes256ctr_get_length(const uint8_t key[PC_AES256CTR_KEY_LEN], const uint8_t counter[PC_AES256CTR_CTR_LEN], const uint8_t enc4[4])
Decrypt only the 4-byte SSH packet_length prefix WITHOUT advancing counter.
AES-256-CTR stream cipher (aes256-ctr, RFC 4344 ยง4) - stateless one-shot API.
#define PC_AES256CTR_CTR_LEN
AES-256-CTR counter/IV block length (bytes).
Definition aes256ctr.h:46
#define PC_AES256CTR_KEY_LEN
AES-256-CTR key length (bytes).
Definition aes256ctr.h:44
Compact table-free software AES key schedule + single-block encrypt (FIPS 197) - one source of truth.
void pc_aes_key_expand(const uint8_t *key, int nk, uint32_t *rk)
AES key expansion (FIPS 197 sec 5.2). nk key words (4=AES-128, 8=AES-256); rk receives 4*(nk + 7) rou...
Definition aes_block.h:52
void pc_aes_encrypt_block(const uint32_t *rk, int nr, const uint8_t in[16], uint8_t out[16])
AES single-block encrypt (FIPS 197 sec 5.1), nr rounds (10=AES-128, 14=AES-256). State is column-majo...
Definition aes_block.h:82
A secure borrow whose acquire and release are one call each.
Definition secure.h:153
const pc_span & span() const
The borrowed region; empty if the pool could not satisfy it.
Definition secure.h:161
Per-translation-unit optimization override for hot, pure-integer crypto.
#define PC_WORK_AES256CTR
Secure pool accessor - borrows that hold key material.
bool pc_span_ok(const pc_span &s)
True when the span refers to real storage and every write so far has fit.
Definition span.h:114
mbedtls_aes_context aes
Definition aes256ctr.cpp:38
uint8_t ks[16]
Definition aes256ctr.cpp:39
A writable byte region: the storage, the capacity that belongs to it, and what has been produced into...
Definition span.h:65
uint8_t * buf
first byte, or nullptr when the region could not be obtained
Definition span.h:66