DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
ssh_dh.h File Reference

DH-group14-SHA256 key exchange (RFC 4253 §8 + RFC 8268). More...

Go to the source code of this file.

Macros

#define SSH_KDF_MAX   (4 * SSH_SHA256_DIGEST_LEN)
 Max bytes ssh_kdf_derive() can produce (4 SHA-256 blocks).
 

Functions

void ssh_rng_fill (uint8_t *buf, size_t len)
 Fill len bytes of buf with cryptographically random data.
 
int ssh_dh_generate (uint8_t i)
 Generate the server ephemeral DH key pair for connection slot i.
 
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.
 
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).
 
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.
 

Detailed Description

DH-group14-SHA256 key exchange (RFC 4253 §8 + RFC 8268).

═══════════════════════════════════════════════════════════════════════════ PROTOCOL FLOW (server side) ═══════════════════════════════════════════════════════════════════════════

Client Server ────── ────── SSH_MSG_KEXDH_INIT ──e──► ssh_dh_generate(slot): y = random 2048-bit scalar f = 2^y mod p (server public) (y, f stored in ssh_dh[slot])

ssh_kexdh_handle(slot, e): validate e: 1 < e < p-1 K = e^y mod p (shared secret) H = SHA256(V_C||V_S||I_C||I_S||K_S||e||f||K) sig = RSA-SHA2-256(host_key, H) ◄────── SSH_MSG_KEXDH_REPLY (K_S, f, sig) y zeroed; K → key derivation → K zeroed

═══════════════════════════════════════════════════════════════════════════ SECURITY NOTES ═══════════════════════════════════════════════════════════════════════════

  1. Private scalar y is generated fresh for each connection. It is stored in ssh_dh[slot].y and zeroed by ssh_dh_wipe() (in ssh_keymat.h) as soon as K is derived. NEVER reuse y.
  2. The received value e is validated (1 < e < p-1) before any computation. A value of 1 or p-1 is a known small-subgroup attack; reject both (RFC 4253 §8 requires rejection of e outside [2, p-2]).
  3. The exchange hash H is computed over all four handshake strings (V_C, V_S, I_C, I_S), the host key blob K_S, and the DH values. Omitting any field or reordering them breaks the binding between the cryptographic material and the identity of both sides.
  4. Key material derivation follows RFC 4253 §7.2. K and H are the only inputs; the session_id equals H from the first KEX.
Author
Douglas Quigg (dstroy0)
Date
2026

Definition in file ssh_dh.h.

Macro Definition Documentation

◆ SSH_KDF_MAX

#define SSH_KDF_MAX   (4 * SSH_SHA256_DIGEST_LEN)

Max bytes ssh_kdf_derive() can produce (4 SHA-256 blocks).

Definition at line 129 of file ssh_dh.h.

Function Documentation

◆ ssh_rng_fill()

void ssh_rng_fill ( uint8_t *  buf,
size_t  len 
)

Fill len bytes of buf with cryptographically random data.

On Arduino: uses esp_fill_random() (hardware RNG seeded by analog noise). On native: uses esp_fill_random() from the Arduino.h mock, which is a time-seeded PRNG - NOT secure, for testing only.

Definition at line 18 of file ssh_dh.cpp.

Referenced by ssh_dh_generate(), ssh_kex_generate(), and ssh_kexinit_build().

◆ ssh_dh_generate()

int ssh_dh_generate ( uint8_t  i)

Generate the server ephemeral DH key pair for connection slot i.

Fills ssh_dh[i].y with random bytes (2048-bit private scalar), then computes ssh_dh[i].f = 2^y mod p (the server's public DH value).

The caller must send f in SSH_MSG_KEXDH_REPLY after this returns.

Parameters
iSSH connection slot index.
Returns
0 on success, -1 if i is out of range.

Definition at line 27 of file ssh_dh.cpp.

References bn_expmod_group14(), SshBigNum::d, SshDhState::f, group14_g, SshDhState::kex_done, MAX_SSH_CONNS, SSH_BN_LIMBS, ssh_dh, ssh_rng_fill(), and SshDhState::y.

Referenced by ssh_kex_generate().

◆ ssh_dh_derive_keys()

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.

Implements RFC 4253 §7.2 key derivation: IV_c2s = SHA256(K || H || 'A' || session_id) [first 16 bytes] IV_s2c = SHA256(K || H || 'B' || session_id) [first 16 bytes] key_c2s = SHA256(K || H || 'C' || session_id) [32 bytes] key_s2c = SHA256(K || H || 'D' || session_id) [32 bytes] mac_c2s = SHA256(K || H || 'E' || session_id) [32 bytes] mac_s2c = SHA256(K || H || 'F' || session_id) [32 bytes]

Installs the derived keys into ssh_keys[i]. Does NOT zero K or H - the caller does that.

Parameters
iSlot index.
K_beShared secret K, big-endian, 256 bytes.
HExchange hash, 32 bytes (also used as session_id).

Definition at line 221 of file ssh_dh.cpp.

References SSH_CIPHER_AES256CTR, ssh_dh_derive_keys_sid(), and SSH_MAC_HMAC_SHA256.

◆ ssh_dh_derive_keys_sid()

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).

Same as ssh_dh_derive_keys() but uses session_id for the session-id component of every key, which on a re-key must remain the exchange hash H from the first KEX (while H is the current re-key exchange hash).

Parameters
iSlot index.
K_beShared secret K, big-endian, 256 bytes.
HCurrent exchange hash, 32 bytes.
session_idSession id (H of the first KEX), 32 bytes.
k_is_stringEncode K as a plain SSH string (hybrid KEX) instead of an mpint (classical).

Definition at line 151 of file ssh_dh.cpp.

References SshKeyMat::active, SshKeyMat::c2s_ctx, SshKeyMat::chacha_key_c2s, SshKeyMat::chacha_key_s2c, SshKeyMat::cipher_mode, SshKeyMat::gcm_c2s, SshKeyMat::gcm_s2c, SshKeyMat::mac_key_c2s, SshKeyMat::mac_key_s2c, SshKeyMat::mac_mode, MAX_SSH_CONNS, SshKeyMat::s2c_ctx, ssh_aes256ctr_init(), ssh_aesgcm_init(), SSH_CHACHAPOLY_KEY_LEN, SSH_CIPHER_AES256GCM, SSH_CIPHER_CHACHA20POLY1305, ssh_kdf_derive(), ssh_keys, and SSH_SHA256_DIGEST_LEN.

Referenced by ssh_dh_derive_keys(), and ssh_kexdh_handle().

◆ ssh_kdf_derive()

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.

Produces K1 || K2 || ... where K1 = HASH(K || H || label || session_id) and each Ki+1 = HASH(K || H || K1..Ki), filling out (out_len bytes). K is encoded as an mpint (classical KEX) or, when k_is_string, a plain 32-byte SSH string (the mlkem768x25519-sha256 hybrid). Every algorithm negotiated today needs <= 32 B (one block); the chain exists for spec-completeness / future ciphers needing longer key material. out_len is clamped to SSH_KDF_MAX.

Definition at line 110 of file ssh_dh.cpp.

References SSH_KDF_MAX, SSH_SHA256_DIGEST_LEN, ssh_sha256_final(), ssh_sha256_init(), and ssh_sha256_update().

Referenced by ssh_dh_derive_keys_sid().