ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ssh_kexhash.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 ssh_kexhash.h
6 * @brief One key-exchange digest that dispatches SHA-256 or SHA-512 by the negotiated method.
7 *
8 * RFC 4253 sec 8 ties the exchange hash H (and the sec 7.2 key derivation) to the KEX method's hash:
9 * the `-sha256` methods (curve25519-sha256, ecdh-sha2-nistp256, dh-group14-sha256,
10 * mlkem768x25519-sha256) use SHA-256; `-sha512` methods (sntrup761x25519-sha512@openssh.com) use
11 * SHA-512. Rather than fork every hash site, the exchange hash and the KDF run through this one
12 * namespace, so adding a KEX with a different hash is a one-line change and not a new code path.
13 *
14 * @author Douglas Quigg (dstroy0)
15 * @date 2026
16 */
17
18#ifndef PROTOCORE_SSH_KEXHASH_H
19#define PROTOCORE_SSH_KEXHASH_H
20
21#include "protocore_config.h" // the entry point: protocore_types.h for the widths
22
24
25/** @brief Longest exchange hash / session_id the two KEX hashes produce (SHA-512). */
26#define SSH_KEXHASH_MAX_LEN 64
27
28// PROTOCORE_SSH_KEXHASH_BORROW - the bytes one digest runs out of - is stated in protocore_config.h,
29// which sums it into the secure arena. A caller takes them once and passes the pointer to every call.
30
31/** @brief Which of the two KEX hashes the negotiated method binds a digest to. */
32typedef struct
33{
34 proto_bool is512; ///< true for a -sha512 method, false for a -sha256 one
36
37/** @brief The bytes absorbed. */
38typedef struct
39{
40 const uint8_t *data; ///< the bytes
41 size_t len; ///< how many
43
44/** @brief Where the digest lands. */
45typedef struct
46{
47 uint8_t *out; ///< SSH_KEXHASH_MAX_LEN bytes; the bound hash writes its own length
49
50/**
51 * @brief The SSH key-exchange digest (RFC 4253 sec 8), bound to the KEX method's hash.
52 *
53 * A caller sets the members a call takes, invokes it through ::SshKexHash with the bytes it runs out
54 * of, and reads the outcome off the same handle. How those bytes are carved is this module's and is
55 * never named here.
56 *
57 * SshKexHash.init_args.is512 = is512;
58 * SshKexHash.init(work);
59 * SshKexHash.update_args.data = v_c;
60 * SshKexHash.update_args.len = v_c_len;
61 * SshKexHash.update(work);
62 * SshKexHash.final_args.out = H;
63 * SshKexHash.final(work);
64 * // SshKexHash.len is 32 or 64, the length written
65 *
66 * @var SshKexHashNs::init_args which of the two KEX hashes the negotiated method binds this to
67 * @var SshKexHashNs::update_args the bytes absorbed
68 * @var SshKexHashNs::final_args where the digest lands
69 * @var SshKexHashNs::ok a call's true/false outcome; false on a null pointer
70 * @var SshKexHashNs::len the bound hash's digest length, 32 or 64, from @ref SshKexHashNs::init
71 * @var SshKexHashNs::init bind the digest to the negotiated method's hash and start it
72 * @var SshKexHashNs::update absorb the staged bytes
73 * @var SshKexHashNs::final write @ref SshKexHashNs::len octets of digest
74 *
75 * @c work is PROTOCORE_SSH_KEXHASH_BORROW secure bytes the CALLER took, at an address it knows. It
76 * is not held past the call, so nothing here aliases it. The caller releases
77 * it, and the pool wipes on release; this module neither takes it, holds it, releases it, nor wipes
78 * it. The exchange hash and every key the sec 7.2 chain derives pass through those bytes, so they die
79 * with the release rather than on the stack. Two digests are two borrows and never collide.
80 *
81 * No storage member and no context: a caller sets operands and reads @ref SshKexHashNs::ok, and that
82 * is all the surface there is.
83 */
92
93/** @brief The operands and the outcome. */
95
96/** @brief The entries. */
97typedef struct
98{
99 void (*const init)(uint8_t *work);
100 void (*const update)(uint8_t *work);
101 void (*const final)(uint8_t *work);
103
104// What the table binds, defined once in the .c and taking one parameter each: everything
105// else an entry needs is an operand in SshKexHashV or a region of the borrow at a fixed offset.
106void protocore_ssh_kex_hash_init(uint8_t *work);
109
110// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
111// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
112// `SshKexHash.init(work)` resolves to a named function and becomes a DIRECT call. An extern table
113// leaves the call indirect and the symbol live at every level, -O2 -flto included.
114static const SshKexHashNs SshKexHash __attribute__((unused)) = {
118};
119
121
122#endif // PROTOCORE_SSH_KEXHASH_H
void protocore_ssh_kex_hash_update(uint8_t *work)
SshKexHashVars SshKexHashV
The operands and the outcome.
void protocore_ssh_kex_hash_final(uint8_t *work)
void protocore_ssh_kex_hash_init(uint8_t *work)
Where the digest lands.
Definition ssh_kexhash.h:46
uint8_t * out
SSH_KEXHASH_MAX_LEN bytes; the bound hash writes its own length.
Definition ssh_kexhash.h:47
Which of the two KEX hashes the negotiated method binds a digest to.
Definition ssh_kexhash.h:33
proto_bool is512
true for a -sha512 method, false for a -sha256 one
Definition ssh_kexhash.h:34
The entries.
Definition ssh_kexhash.h:98
void(*const init)(uint8_t *work)
Definition ssh_kexhash.h:99
The bytes absorbed.
Definition ssh_kexhash.h:39
const uint8_t * data
the bytes
Definition ssh_kexhash.h:40
size_t len
how many
Definition ssh_kexhash.h:41
proto_bool ok
Definition ssh_kexhash.h:89
SshKexHashUpdateArgs update_args
Definition ssh_kexhash.h:87
SshKexHashInitArgs init_args
Definition ssh_kexhash.h:86
SshKexHashFinalArgs final_args
Definition ssh_kexhash.h:88
#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