ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
chachapoly.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 chachapoly.h
6 * @brief chacha20-poly1305@openssh.com AEAD cipher (OpenSSH PROTOCOL.chacha20poly1305).
7 *
8 * OpenSSH's authenticated cipher for the SSH binary packet. The 512-bit key is split into two
9 * 256-bit ChaCha20 keys: K_main = key[0..32] encrypts the packet payload, K_header = key[32..64]
10 * encrypts the 4-byte packet-length field separately (so a receiver can size the packet before it
11 * has the whole thing). The nonce for both is the packet sequence number as a big-endian uint64.
12 *
13 * - Poly1305 key = first 32 bytes of ChaCha20(K_main, seqnr, counter 0)
14 * - encrypted length = ChaCha20(K_header, seqnr, counter 0) XOR length
15 * - encrypted payload = ChaCha20(K_main, seqnr, counter 1) XOR payload
16 * - tag = Poly1305(encrypted_length || encrypted_payload) (16 bytes, appended)
17 *
18 * On decrypt the tag is verified (constant-time) before any plaintext is produced. Pure, no heap.
19 *
20 * @author Douglas Quigg (dstroy0)
21 * @date 2026
22 */
23
24#ifndef PROTOCORE_CHACHAPOLY_H
25#define PROTOCORE_CHACHAPOLY_H
26
27#include "protocore_config.h" // the entry point: protocore_types.h for proto_bool and the widths
28
29#if PROTOCORE_ENABLE_CHACHAPOLY
30
32
33#define PROTOCORE_CHACHAPOLY_KEY_LEN 64 ///< two 256-bit ChaCha20 keys
34#define PROTOCORE_CHACHAPOLY_TAG_LEN 16 ///< Poly1305 tag
35#define PROTOCORE_CHACHAPOLY_AAD_LEN 4 ///< the encrypted packet-length field
36
37// PROTOCORE_CHACHAPOLY_BORROW - the bytes one packet operation runs out of - is stated in
38// protocore_config.h, which sums it into the secure arena. A caller takes them once and passes the
39// pointer to every call.
40
41/** @brief The encrypted length field a length peek reads. */
42typedef struct
43{
44 const uint8_t *key; ///< PROTOCORE_CHACHAPOLY_KEY_LEN bytes
45 const uint8_t *enc_len; ///< PROTOCORE_CHACHAPOLY_AAD_LEN encrypted length bytes
46 uint32_t seqnr; ///< packet sequence number, the ChaCha nonce
47} ChachaPolyLengthArgs;
48
49/** @brief The packet an encryption covers. */
50typedef struct
51{
52 const uint8_t *key; ///< PROTOCORE_CHACHAPOLY_KEY_LEN bytes
53 const uint8_t *src; ///< plaintext: 4-byte packet length (big-endian) || payload_len payload bytes
54 uint8_t *dest; ///< encrypted length (4) || encrypted payload (payload_len) || tag (16); may alias src
55 uint32_t seqnr; ///< packet sequence number, the ChaCha nonce
56 uint32_t payload_len; ///< payload bytes following the length field
57} ChachaPolyEncryptArgs;
58
59/** @brief The packet a decryption verifies. */
60typedef struct
61{
62 const uint8_t *key; ///< PROTOCORE_CHACHAPOLY_KEY_LEN bytes
63 const uint8_t *src; ///< ciphertext: encrypted length (4) || encrypted payload (payload_len) || tag (16)
64 uint8_t *dest; ///< plaintext length (4) || plaintext payload (payload_len); may alias src
65 uint32_t seqnr; ///< packet sequence number, the ChaCha nonce
66 uint32_t payload_len; ///< payload bytes following the length field
67} ChachaPolyDecryptArgs;
68
69/**
70 * @brief chacha20-poly1305@openssh.com (OpenSSH PROTOCOL.chacha20poly1305).
71 *
72 * A caller sets the members a call takes, invokes it through ::ChachaPoly with the bytes it runs out of, and
73 * reads the outcome off the same handle. How those bytes are carved is this module's and is never named here.
74 *
75 * ChachaPoly.length_args.key = key;
76 * ChachaPoly.length_args.enc_len = pkt;
77 * ChachaPoly.length_args.seqnr = seqnr;
78 * ChachaPoly.get_length(work);
79 * // ChachaPoly.length now holds the packet_length
80 *
81 * @var ChachaPolyNs::length_args the encrypted length field a length peek reads
82 * @var ChachaPolyNs::encrypt_args the packet an encryption covers
83 * @var ChachaPolyNs::decrypt_args the packet a decryption verifies
84 * @var ChachaPolyNs::ok a call's true/false outcome; false on a null pointer, and on a tag mismatch
85 * @var ChachaPolyNs::length the SSH packet_length the last peek recovered: bytes after the length
86 * field, excluding the tag
87 * @var ChachaPolyNs::get_length decrypt the 4-byte length field to size the packet before reading its body
88 * @var ChachaPolyNs::encrypt encrypt and authenticate one packet, tag appended
89 * @var ChachaPolyNs::decrypt verify the tag, then decrypt; no plaintext is produced unless it verified
90 *
91 * @c work is PROTOCORE_CHACHAPOLY_BORROW secure bytes the CALLER took, at an address it knows. It is not held past the
92 * call, so nothing here aliases it. The caller releases it, and the pool wipes on release; this module neither takes
93 * it, holds it, releases it, nor wipes it. That is what keeps the one-time Poly1305 key derived per packet from
94 * outliving the caller. A connection takes those bytes once for its slot and passes them on every packet.
95 *
96 * No storage member and no context: a caller sets operands and reads @ref ChachaPolyNs::ok, and that is all the
97 * surface there is.
98 */
99typedef struct
100{
101 ChachaPolyLengthArgs length_args;
102 ChachaPolyEncryptArgs encrypt_args;
103 ChachaPolyDecryptArgs decrypt_args;
104 proto_bool ok;
105 uint32_t length;
106} ChachaPolyVars;
107
108/** @brief The operands and the outcome. */
109extern ChachaPolyVars ChachaPolyV;
110
111/** @brief The entries. */
112typedef struct
113{
114 void (*const get_length)(uint8_t *work);
115 void (*const encrypt)(uint8_t *work);
116 void (*const decrypt)(uint8_t *work);
117} ChachaPolyNs;
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 ChachaPolyV or a region of the borrow at a fixed offset.
121void protocore_chacha_poly_get_length(uint8_t *work);
122void protocore_chacha_poly_encrypt(uint8_t *work);
123void protocore_chacha_poly_decrypt(uint8_t *work);
124
125// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
126// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
127// `ChachaPoly.get_length(work)` resolves to a named function and becomes a DIRECT call. An extern table
128// leaves the call indirect and the symbol live at every level, -O2 -flto included.
129static const ChachaPolyNs ChachaPoly __attribute__((unused)) = {
130 .get_length = protocore_chacha_poly_get_length,
131 .encrypt = protocore_chacha_poly_encrypt,
132 .decrypt = protocore_chacha_poly_decrypt,
133};
134
136
137#endif // PROTOCORE_ENABLE_CHACHAPOLY
138
139#endif // PROTOCORE_CHACHAPOLY_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