ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
aes256ctr.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 aes256ctr.h
6 * @brief AES-256-CTR stream cipher (aes256-ctr, RFC 4344 §4).
7 *
8 * The mandatory cipher for this SSH implementation. CTR mode turns AES into a stream cipher: each
9 * 16-byte counter block is AES-ECB encrypted into a keystream block and the data is XOR'd with it, so
10 * encrypt and decrypt are the identical operation. The entries below are one surface over both arms:
11 * a part with an AES peripheral runs the block on it, a part without runs the FIPS 197 rounds.
12 *
13 * The two things that persist across packets are the caller's: the 32-byte key and the 16-byte
14 * counter, passed in on every call. The counter advances in place by ceil(len / 16) blocks, so
15 * successive calls continue the same stream.
16 *
17 * COUNTER FORMAT (RFC 4344 §4)
18 * The 16-byte counter increments as a big-endian 128-bit integer after each 16-byte keystream block.
19 * The initial counter is the IV from the key exchange (RFC 4253 §7.2, labels 'A'/'B').
20 *
21 * @note The SSH binary packet is always a whole number of cipher blocks, so every call is block-aligned
22 * and the counter alone is sufficient state. A non-block-aligned length is permitted only as the
23 * final call of a stream (any leftover keystream in the last block is discarded, not carried).
24 *
25 * @author Douglas Quigg (dstroy0)
26 * @date 2026
27 */
28
29#ifndef PROTOCORE_AES256CTR_H
30#define PROTOCORE_AES256CTR_H
31
32#include "protocore_config.h" // the entry point: protocore_types.h for the widths and PROTOCORE_BEGIN_DECLS
33
34#if PROTOCORE_ENABLE_AES256CTR
35
37
38/** @brief AES-256-CTR key length (bytes). */
39#define PROTOCORE_AES256CTR_KEY_LEN 32
40/** @brief AES-256-CTR counter/IV block length (bytes). */
41#define PROTOCORE_AES256CTR_CTR_LEN 16
42
43// PROTOCORE_AES256CTR_BORROW - the bytes a cipher call runs out of - is stated in protocore_config.h,
44// which sums it into the secure arena. A caller takes them once and passes the pointer to every call.
45
46/** @brief The key, the counter and the buffers one CTR call runs over. */
47typedef struct
48{
49 const uint8_t *key; ///< PROTOCORE_AES256CTR_KEY_LEN bytes
50 uint8_t *counter; ///< PROTOCORE_AES256CTR_CTR_LEN bytes, big-endian, advanced in place
51 const uint8_t *in; ///< the input bytes
52 uint8_t *out; ///< where they land; may equal @c in
53 size_t len; ///< how many
54} Aes256CtrCryptArgs;
55
56/** @brief The key, the counter and the four encrypted length bytes a peek reads. */
57typedef struct
58{
59 const uint8_t *key; ///< PROTOCORE_AES256CTR_KEY_LEN bytes
60 const uint8_t *counter; ///< PROTOCORE_AES256CTR_CTR_LEN bytes, read and not advanced
61 const uint8_t *enc4; ///< the 4 encrypted length bytes at the start of the packet
62} Aes256CtrGetLengthArgs;
63
64/**
65 * @brief AES-256-CTR (RFC 4344 §4).
66 *
67 * A caller sets the members a call takes, invokes it through ::Aes256Ctr with the bytes it runs out
68 * of, and reads the outcome off the same handle. How those bytes are carved is this module's and is
69 * never named here.
70 *
71 * Aes256Ctr.crypt_args.key = key;
72 * Aes256Ctr.crypt_args.counter = ctr;
73 * Aes256Ctr.crypt_args.in = pkt;
74 * Aes256Ctr.crypt_args.out = pkt;
75 * Aes256Ctr.crypt_args.len = pkt_len;
76 * Aes256Ctr.crypt(work);
77 *
78 * @var Aes256CtrNs::crypt_args the key, the counter and the buffers one CTR call runs over
79 * @var Aes256CtrNs::get_length_args the key, the counter and the four encrypted length bytes a peek reads
80 * @var Aes256CtrNs::ok a call's true/false outcome
81 * @var Aes256CtrNs::length the SSH packet_length the last peek recovered
82 * @var Aes256CtrNs::crypt XOR the CTR keystream over the buffer, advancing the counter
83 * @var Aes256CtrNs::get_length decrypt the 4-byte packet_length prefix without advancing the counter
84 *
85 * @ref Aes256CtrNs::crypt encrypts and decrypts with the same body, and @c crypt_args.in and
86 * @c crypt_args.out may alias, which is the in-place form the SSH packet layer uses.
87 *
88 * @ref Aes256CtrNs::get_length leaves @c get_length_args.counter where it was, so a receiver learns a
89 * packet's length - and thus how many bytes to wait for - before the whole packet has arrived, without
90 * consuming counter state.
91 *
92 * @c work is PROTOCORE_AES256CTR_BORROW secure bytes the CALLER took, at an address it knows. It
93 * is not held past the call, so nothing here aliases it. The caller releases
94 * it, and the pool wipes on release; this module neither takes it, holds it, releases it, nor wipes
95 * it. The expanded key schedule lives in those bytes and nowhere else, so it never reaches BSS or the
96 * stack. Two ciphers running at once are two borrows and never collide.
97 *
98 * No storage member and no context: a caller sets operands and reads @ref Aes256CtrNs::ok, and that is
99 * all the surface there is.
100 */
101typedef struct
102{
103 Aes256CtrCryptArgs crypt_args;
104 Aes256CtrGetLengthArgs get_length_args;
105 proto_bool ok;
106 uint32_t length;
107} Aes256CtrVars;
108
109/** @brief The operands and the outcome. */
110extern Aes256CtrVars Aes256CtrV;
111
112/** @brief The entries. */
113typedef struct
114{
115 void (*const crypt)(uint8_t *work);
116 void (*const get_length)(uint8_t *work);
117} Aes256CtrNs;
118
119// What the table binds, defined once in the .c and taking one parameter each: everything
120// else an entry needs is an operand in Aes256CtrV or a region of the borrow at a fixed offset.
121void protocore_aes256_ctr_crypt(uint8_t *work);
122void protocore_aes256_ctr_get_length(uint8_t *work);
123
124// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
125// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
126// `Aes256Ctr.crypt(work)` resolves to a named function and becomes a DIRECT call. An extern table
127// leaves the call indirect and the symbol live at every level, -O2 -flto included.
128static const Aes256CtrNs Aes256Ctr __attribute__((unused)) = {
129 .crypt = protocore_aes256_ctr_crypt,
130 .get_length = protocore_aes256_ctr_get_length,
131};
132
134
135#endif // PROTOCORE_ENABLE_AES256CTR
136
137#endif // PROTOCORE_AES256CTR_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