DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
ssh_dh.h
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 ssh_dh.h
6 * @brief DH-group14-SHA256 key exchange (RFC 4253 §8 + RFC 8268).
7 *
8 * ═══════════════════════════════════════════════════════════════════════════
9 * PROTOCOL FLOW (server side)
10 * ═══════════════════════════════════════════════════════════════════════════
11 *
12 * Client Server
13 * ────── ──────
14 * SSH_MSG_KEXDH_INIT ──e──► ssh_dh_generate(slot):
15 * y = random 2048-bit scalar
16 * f = 2^y mod p (server public)
17 * (y, f stored in ssh_dh[slot])
18 *
19 * ssh_kexdh_handle(slot, e):
20 * validate e: 1 < e < p-1
21 * K = e^y mod p (shared secret)
22 * H = SHA256(V_C||V_S||I_C||I_S||K_S||e||f||K)
23 * sig = RSA-SHA2-256(host_key, H)
24 * ◄────── SSH_MSG_KEXDH_REPLY (K_S, f, sig)
25 * y zeroed; K → key derivation → K zeroed
26 *
27 * ═══════════════════════════════════════════════════════════════════════════
28 * SECURITY NOTES
29 * ═══════════════════════════════════════════════════════════════════════════
30 *
31 * 1. Private scalar y is generated fresh for each connection. It is stored
32 * in ssh_dh[slot].y and zeroed by ssh_dh_wipe() (in ssh_keymat.h) as
33 * soon as K is derived. NEVER reuse y.
34 *
35 * 2. The received value e is validated (1 < e < p-1) before any computation.
36 * A value of 1 or p-1 is a known small-subgroup attack; reject both
37 * (RFC 4253 §8 requires rejection of e outside [2, p-2]).
38 *
39 * 3. The exchange hash H is computed over all four handshake strings
40 * (V_C, V_S, I_C, I_S), the host key blob K_S, and the DH values.
41 * Omitting any field or reordering them breaks the binding between the
42 * cryptographic material and the identity of both sides.
43 *
44 * 4. Key material derivation follows RFC 4253 §7.2. K and H are the only
45 * inputs; the session_id equals H from the first KEX.
46 *
47 * @author Douglas Quigg (dstroy0)
48 * @date 2026
49 */
50
51#ifndef DETERMINISTICESPASYNCWEBSERVER_SSH_DH_H
52#define DETERMINISTICESPASYNCWEBSERVER_SSH_DH_H
53
54#include "ServerConfig.h"
58#include <stddef.h>
59#include <stdint.h>
60
61// ---------------------------------------------------------------------------
62// RNG
63// ---------------------------------------------------------------------------
64
65/**
66 * @brief Fill @p len bytes of @p buf with cryptographically random data.
67 *
68 * On Arduino: uses esp_fill_random() (hardware RNG seeded by analog noise).
69 * On native: uses esp_fill_random() from the Arduino.h mock, which is a
70 * time-seeded PRNG - NOT secure, for testing only.
71 */
72void ssh_rng_fill(uint8_t *buf, size_t len);
73
74// ---------------------------------------------------------------------------
75// DH key exchange
76// ---------------------------------------------------------------------------
77
78/**
79 * @brief Generate the server ephemeral DH key pair for connection slot @p i.
80 *
81 * Fills ssh_dh[i].y with random bytes (2048-bit private scalar), then
82 * computes ssh_dh[i].f = 2^y mod p (the server's public DH value).
83 *
84 * The caller must send f in SSH_MSG_KEXDH_REPLY after this returns.
85 *
86 * @param i SSH connection slot index.
87 * @return 0 on success, -1 if @p i is out of range.
88 */
89int ssh_dh_generate(uint8_t i);
90
91/**
92 * @brief Derive the six session keys from shared secret K and exchange hash H.
93 *
94 * Implements RFC 4253 §7.2 key derivation:
95 * IV_c2s = SHA256(K || H || 'A' || session_id) [first 16 bytes]
96 * IV_s2c = SHA256(K || H || 'B' || session_id) [first 16 bytes]
97 * key_c2s = SHA256(K || H || 'C' || session_id) [32 bytes]
98 * key_s2c = SHA256(K || H || 'D' || session_id) [32 bytes]
99 * mac_c2s = SHA256(K || H || 'E' || session_id) [32 bytes]
100 * mac_s2c = SHA256(K || H || 'F' || session_id) [32 bytes]
101 *
102 * Installs the derived keys into ssh_keys[i].
103 * Does NOT zero K or H - the caller does that.
104 *
105 * @param i Slot index.
106 * @param K_be Shared secret K, big-endian, 256 bytes.
107 * @param H Exchange hash, 32 bytes (also used as session_id).
108 */
109void ssh_dh_derive_keys(uint8_t i, const uint8_t K_be[256], const uint8_t H[SSH_SHA256_DIGEST_LEN]);
110
111/**
112 * @brief Derive session keys with an explicit session id (RFC 4253 §7.2).
113 *
114 * Same as ssh_dh_derive_keys() but uses @p session_id for the session-id
115 * component of every key, which on a re-key must remain the exchange hash H
116 * from the *first* KEX (while @p H is the current re-key exchange hash).
117 *
118 * @param i Slot index.
119 * @param K_be Shared secret K, big-endian, 256 bytes.
120 * @param H Current exchange hash, 32 bytes.
121 * @param session_id Session id (H of the first KEX), 32 bytes.
122 * @param k_is_string Encode K as a plain SSH string (hybrid KEX) instead of an mpint (classical).
123 */
124void ssh_dh_derive_keys_sid(uint8_t i, const uint8_t K_be[256], const uint8_t H[SSH_SHA256_DIGEST_LEN],
125 const uint8_t session_id[SSH_SHA256_DIGEST_LEN], uint8_t cipher_alg, uint8_t mac_alg,
126 bool k_is_string = false);
127
128/** @brief Max bytes ssh_kdf_derive() can produce (4 SHA-256 blocks). */
129#define SSH_KDF_MAX (4 * SSH_SHA256_DIGEST_LEN)
130
131/**
132 * @brief RFC 4253 §7.2 key derivation for any length up to @ref SSH_KDF_MAX.
133 *
134 * Produces K1 || K2 || ... where K1 = HASH(K || H || @p label || session_id)
135 * and each Ki+1 = HASH(K || H || K1..Ki), filling @p out (@p out_len bytes). K is
136 * encoded as an mpint (classical KEX) or, when @p k_is_string, a plain 32-byte SSH
137 * string (the mlkem768x25519-sha256 hybrid). Every algorithm negotiated today needs
138 * <= 32 B (one block); the chain exists for spec-completeness / future ciphers needing
139 * longer key material. @p out_len is clamped to SSH_KDF_MAX.
140 */
141void ssh_kdf_derive(const uint8_t K_be[256], const uint8_t H[SSH_SHA256_DIGEST_LEN],
142 const uint8_t session_id[SSH_SHA256_DIGEST_LEN], char label, uint8_t *out, size_t out_len,
143 bool k_is_string = false);
144
145#endif // DETERMINISTICESPASYNCWEBSERVER_SSH_DH_H
User-facing configuration for DeterministicESPAsyncWebServer.
2048-bit big-integer arithmetic for DH-group14 and RSA-2048.
void ssh_dh_derive_keys(uint8_t i, const uint8_t K_be[256], const uint8_t H[SSH_SHA256_DIGEST_LEN])
Derive the six session keys from shared secret K and exchange hash H.
Definition ssh_dh.cpp:221
void ssh_dh_derive_keys_sid(uint8_t i, const uint8_t K_be[256], const uint8_t H[SSH_SHA256_DIGEST_LEN], const uint8_t session_id[SSH_SHA256_DIGEST_LEN], uint8_t cipher_alg, uint8_t mac_alg, bool k_is_string=false)
Derive session keys with an explicit session id (RFC 4253 §7.2).
Definition ssh_dh.cpp:151
void ssh_rng_fill(uint8_t *buf, size_t len)
Fill len bytes of buf with cryptographically random data.
Definition ssh_dh.cpp:18
void ssh_kdf_derive(const uint8_t K_be[256], const uint8_t H[SSH_SHA256_DIGEST_LEN], const uint8_t session_id[SSH_SHA256_DIGEST_LEN], char label, uint8_t *out, size_t out_len, bool k_is_string=false)
RFC 4253 §7.2 key derivation for any length up to SSH_KDF_MAX.
Definition ssh_dh.cpp:110
int ssh_dh_generate(uint8_t i)
Generate the server ephemeral DH key pair for connection slot i.
Definition ssh_dh.cpp:27
SSH session key material - types, pools, and security model.
SHA-256 (FIPS 180-4) - streaming context and one-shot API.
#define SSH_SHA256_DIGEST_LEN
SHA-256 digest length in bytes.
Definition ssh_sha256.h:29