|
DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
|
SSH transport handshake - banner exchange and KEXINIT negotiation. More...
#include "network_drivers/presentation/ssh/transport/ssh_transport.h"#include "network_drivers/presentation/ssh/crypto/ssh_bignum.h"#include "network_drivers/presentation/ssh/crypto/ssh_curve25519.h"#include "network_drivers/presentation/ssh/crypto/ssh_ecdsa.h"#include "network_drivers/presentation/ssh/crypto/ssh_ed25519.h"#include "network_drivers/presentation/ssh/crypto/ssh_rsa.h"#include "network_drivers/presentation/ssh/crypto/ssh_sha256.h"#include "network_drivers/presentation/ssh/transport/ssh_dh.h"#include "network_drivers/presentation/ssh/transport/ssh_packet.h"#include "services/clock.h"#include <stdio.h>#include <string.h>Go to the source code of this file.
Classes | |
| struct | SshTransportCtx |
| struct | AlgCand< E > |
Functions | |
| void | ssh_kex_set_prefer_rsa (bool prefer) |
| Steer KEX / host-key negotiation toward RSA + DH-group14 (default) or toward the modern curve25519 + ed25519 suite. | |
| bool | ssh_kex_prefer_rsa (void) |
| Current negotiation preference (true = prefer RSA/DH, the ESP32-accelerated path). | |
| void | ssh_hostkey_ed25519_set (const uint8_t seed[32]) |
| Install an ssh-ed25519 host key from its 32-byte seed (RFC 8032 private key). | |
| bool | ssh_hostkey_ed25519_available (void) |
| True if an ssh-ed25519 host key has been installed. | |
| void | ssh_hostkey_ecdsa_set (const uint8_t priv[SSH_ECDSA_P256_PRIV_LEN]) |
| bool | ssh_hostkey_ecdsa_available (void) |
| True if an ecdsa-sha2-nistp256 host key has been installed. | |
| void | ssh_transport_init (uint8_t i) |
Reset transport state for slot i to the start of a handshake. | |
| int | ssh_transport_server_banner (uint8_t *out, size_t *out_len, size_t cap) |
Write the server identification string ("SSH-2.0-…\r\n") to out. | |
| int | ssh_transport_recv_banner (uint8_t i, const uint8_t *data, size_t len, size_t *consumed) |
| Feed raw bytes while awaiting the client identification string. | |
| int | ssh_kexinit_build (uint8_t i, uint8_t *payload, size_t *len, size_t cap) |
Build the server KEXINIT payload for slot i (RFC 4253 §7.1). | |
| int | ssh_kexinit_parse (uint8_t i, const uint8_t *payload, size_t len) |
| Parse and negotiate the client KEXINIT payload (RFC 4253 §7.1). | |
| int | ssh_extinfo_build (uint8_t *out, size_t *len, size_t cap) |
| Build SSH_MSG_EXT_INFO advertising server-sig-algs (RFC 8308). | |
| int | ssh_kex_exchange_hash (uint8_t i, const uint8_t *e_be, const uint8_t *f_be, const uint8_t *k_be, const uint8_t *ks, size_t ks_len, uint8_t out[SSH_SHA256_DIGEST_LEN]) |
| Compute the SSH exchange hash H (RFC 4253 §8). | |
| int | ssh_kexdh_parse_init (const uint8_t *payload, size_t len, uint8_t e_be[256]) |
| Parse SSH_MSG_KEXDH_INIT, extracting the client DH value e. | |
| int | ssh_kexdh_build_reply (const uint8_t *ks, size_t ks_len, const uint8_t *f_be, const uint8_t *sig, size_t sig_len, uint8_t *out, size_t *out_len, size_t cap) |
| Build SSH_MSG_KEXDH_REPLY (RFC 4253 §8, RFC 8332 §3). | |
| int | ssh_kex_generate (uint8_t i) |
| Generate the server ephemeral for the negotiated KEX method (call after parse). | |
| int | ssh_kexdh_handle (uint8_t i, const uint8_t *payload, size_t len, uint8_t *reply_out, size_t *reply_len, size_t cap) |
| Handle KEXDH/ECDH_INIT (msg 30) end-to-end and produce the reply payload. | |
| void | ssh_newkeys_sent (uint8_t i) |
| Activate the outbound direction after emitting our SSH_MSG_NEWKEYS. | |
| void | ssh_newkeys_complete (uint8_t i) |
| Complete the NEWKEYS exchange: activate the inbound direction and advance phase. | |
| bool | ssh_rekey_needed (uint8_t i) |
True if slot i has reached the re-key threshold (RFC 4253 §9). | |
| bool | ssh_rekey_due (uint32_t seq_send, uint32_t seq_recv, uint32_t elapsed_ms, uint32_t pkt_threshold, uint32_t time_threshold_ms) |
| Pure re-key decision (RFC 4253 §9: "after each gigabyte ... or after each hour"). | |
| int | ssh_transport_begin_rekey (uint8_t i, uint8_t *out, size_t *out_len, size_t cap) |
| Begin a server-initiated re-key by emitting a fresh KEXINIT. | |
Variables | |
| SshSession | ssh_sess [MAX_SSH_CONNS] |
| Static pool of SSH session state (BSS), one per SSH slot. | |
SSH transport handshake - banner exchange and KEXINIT negotiation.
Definition in file ssh_transport.cpp.
| void ssh_kex_set_prefer_rsa | ( | bool | prefer | ) |
Steer KEX / host-key negotiation toward RSA + DH-group14 (default) or toward the modern curve25519 + ed25519 suite.
On ESP32 the RSA/DH path runs on the hardware MPI accelerator, while curve25519 / ed25519 are software; a device that wants the accelerated handshake keeps the default (prefer RSA), while one that wants modern crypto out of the box calls this with false. The server still advertises both suites (for whatever keys it holds), so a client that only supports one still connects - this only sets the server's preference order.
Runtime-selectable so one firmware can flip per deployment. Default: prefer RSA.
Definition at line 86 of file ssh_transport.cpp.
References SshTransportCtx::prefer_rsa.
| bool ssh_kex_prefer_rsa | ( | void | ) |
Current negotiation preference (true = prefer RSA/DH, the ESP32-accelerated path).
Definition at line 90 of file ssh_transport.cpp.
References SshTransportCtx::prefer_rsa.
| void ssh_hostkey_ed25519_set | ( | const uint8_t | seed[32] | ) |
Install an ssh-ed25519 host key from its 32-byte seed (RFC 8032 private key).
Enables the ssh-ed25519 host-key algorithm for negotiation and derives the public key. The RSA host key (loaded via ssh_rsa) and this may both be present; negotiation picks one per ssh_kex_set_prefer_rsa(). If neither is installed the handshake cannot complete.
Definition at line 95 of file ssh_transport.cpp.
References SshTransportCtx::ed_have, SshTransportCtx::ed_pub, SshTransportCtx::ed_seed, and ssh_ed25519_pubkey().
| bool ssh_hostkey_ed25519_available | ( | void | ) |
True if an ssh-ed25519 host key has been installed.
Definition at line 101 of file ssh_transport.cpp.
References SshTransportCtx::ed_have.
| void ssh_hostkey_ecdsa_set | ( | const uint8_t | priv[SSH_ECDSA_P256_PRIV_LEN] | ) |
Definition at line 105 of file ssh_transport.cpp.
References SshTransportCtx::ecdsa_have, SshTransportCtx::ecdsa_priv, SshTransportCtx::ecdsa_pub, and ssh_ecdsa_p256_pubkey().
| bool ssh_hostkey_ecdsa_available | ( | void | ) |
True if an ecdsa-sha2-nistp256 host key has been installed.
Definition at line 113 of file ssh_transport.cpp.
References SshTransportCtx::ecdsa_have.
| void ssh_transport_init | ( | uint8_t | i | ) |
Reset transport state for slot i to the start of a handshake.
Definition at line 307 of file ssh_transport.cpp.
References MAX_SSH_CONNS, SshSession::phase, SSH_PHASE_BANNER, and ssh_sess.
Referenced by ssh_conn_accept().
| int ssh_transport_server_banner | ( | uint8_t * | out, |
| size_t * | out_len, | ||
| size_t | cap | ||
| ) |
Write the server identification string ("SSH-2.0-…\r\n") to out.
Sent verbatim (not inside a binary packet) at connection start, before any KEXINIT. The CR LF is included on the wire but excluded from V_S used in H.
cap is too small. Definition at line 320 of file ssh_transport.cpp.
References SSH_SERVER_VERSION.
Referenced by ssh_conn_accept().
| int ssh_transport_recv_banner | ( | uint8_t | i, |
| const uint8_t * | data, | ||
| size_t | len, | ||
| size_t * | consumed | ||
| ) |
Feed raw bytes while awaiting the client identification string.
Accumulates bytes until a CR LF (or bare LF) terminates a line beginning with "SSH-". Earlier non-SSH lines (allowed by RFC 4253 §4.2) are skipped. On completion the client version (without CR LF) is stored in v_c.
| [in] | i | SSH slot. |
| [in] | data | Inbound bytes. |
| [in] | len | Number of bytes in data. |
| [out] | consumed | Bytes consumed from data (the banner may be followed immediately by binary packets). |
Definition at line 332 of file ssh_transport.cpp.
References SshSession::banner_buf, SshSession::banner_len, MAX_SSH_CONNS, SshSession::phase, SSH_PHASE_KEXINIT, ssh_sess, SSH_VERSION_MAX, SshSession::v_c, and SshSession::v_c_len.
Referenced by ssh_conn_rx().
| int ssh_kexinit_build | ( | uint8_t | i, |
| uint8_t * | payload, | ||
| size_t * | len, | ||
| size_t | cap | ||
| ) |
Build the server KEXINIT payload for slot i (RFC 4253 §7.1).
Stores a copy in ssh_sess[i].i_s for later exchange-hash computation.
Definition at line 381 of file ssh_transport.cpp.
References SshSession::i_s, SshSession::i_s_len, MAX_SSH_CONNS, SSH_MSG_KEXINIT, ssh_rng_fill(), and ssh_sess.
Referenced by ssh_server_dispatch(), and ssh_transport_begin_rekey().
| int ssh_kexinit_parse | ( | uint8_t | i, |
| const uint8_t * | payload, | ||
| size_t | len | ||
| ) |
Parse and negotiate the client KEXINIT payload (RFC 4253 §7.1).
Stores a copy in ssh_sess[i].i_c. Verifies the client offers every algorithm this server supports (one per category).
Definition at line 492 of file ssh_transport.cpp.
References SshSession::cipher_alg, SshSession::ext_info_c, SshSession::hostkey_alg, SshSession::i_c, SshSession::i_c_len, SshSession::kex_alg, SshSession::mac_alg, MAX_SSH_CONNS, SshSession::phase, SSH_CIPHER_AES256CTR, SSH_CIPHER_AES256GCM, SSH_CIPHER_CHACHA20POLY1305, SSH_KEXINIT_MAX, SSH_MAC_HMAC_SHA256, SSH_MAC_HMAC_SHA256_ETM, SSH_MAC_HMAC_SHA512, SSH_MAC_HMAC_SHA512_ETM, SSH_MSG_KEXINIT, SSH_PHASE_DH_INIT, and ssh_sess.
Referenced by ssh_server_dispatch().
| int ssh_extinfo_build | ( | uint8_t * | out, |
| size_t * | len, | ||
| size_t | cap | ||
| ) |
Build SSH_MSG_EXT_INFO advertising server-sig-algs (RFC 8308).
Tells the client which public-key signature algorithms the server will accept for userauth (rsa-sha2-512, rsa-sha2-256, ssh-ed25519); without it a modern OpenSSH client refuses to sign an RSA key ("no mutual signature algorithm"). Sent once, right after NEWKEYS, when the client advertised ext-info-c.
Definition at line 585 of file ssh_transport.cpp.
References SshTransportCtx::prefer_rsa, and SSH_MSG_EXT_INFO.
Referenced by ssh_server_dispatch().
| int ssh_kex_exchange_hash | ( | uint8_t | i, |
| const uint8_t * | e_be, | ||
| const uint8_t * | f_be, | ||
| const uint8_t * | k_be, | ||
| const uint8_t * | ks, | ||
| size_t | ks_len, | ||
| uint8_t | out[SSH_SHA256_DIGEST_LEN] | ||
| ) |
Compute the SSH exchange hash H (RFC 4253 §8).
H = SHA256( string(V_C) || string(V_S) || string(I_C) || string(I_S) || string(K_S) || mpint(e) || mpint(f) || mpint(K) )
V_C/V_S/I_C/I_S are taken from ssh_sess[i]; the rest are supplied. The 256-byte big-endian integers are re-encoded as SSH mpints (minimal length, leading 0x00 when the high bit is set).
| [in] | i | SSH slot. |
| [in] | e_be | Client DH public value e (256-byte big-endian). |
| [in] | f_be | Server DH public value f (256-byte big-endian). |
| [in] | k_be | Shared secret K (256-byte big-endian). |
| [in] | ks | Server host-key blob K_S. |
| [in] | ks_len | Length of ks. |
| [out] | out | 32-byte exchange hash. |
Definition at line 690 of file ssh_transport.cpp.
| int ssh_kexdh_parse_init | ( | const uint8_t * | payload, |
| size_t | len, | ||
| uint8_t | e_be[256] | ||
| ) |
Parse SSH_MSG_KEXDH_INIT, extracting the client DH value e.
Payload: byte(30) || mpint(e). The mpint is normalized into a fixed 256-byte big-endian buffer (leading sign/zero bytes stripped, right-aligned).
| [in] | payload | KEXDH_INIT payload. |
| [in] | len | Payload length. |
| [out] | e_be | 256-byte big-endian client public value. |
Definition at line 700 of file ssh_transport.cpp.
References SSH_MSG_KEXDH_INIT.
Referenced by ssh_kexdh_handle().
| int ssh_kexdh_build_reply | ( | const uint8_t * | ks, |
| size_t | ks_len, | ||
| const uint8_t * | f_be, | ||
| const uint8_t * | sig, | ||
| size_t | sig_len, | ||
| uint8_t * | out, | ||
| size_t * | out_len, | ||
| size_t | cap | ||
| ) |
Build SSH_MSG_KEXDH_REPLY (RFC 4253 §8, RFC 8332 §3).
Payload: byte(31) || string(K_S) || mpint(f) || string(signature), where the signature blob is string("rsa-sha2-256") || string(sig).
| [in] | ks | Server host-key blob K_S. |
| [in] | ks_len | Length of ks. |
| [in] | f_be | Server DH public value f (256-byte big-endian). |
| [in] | sig | Raw RSA signature over the exchange hash H. |
| [in] | sig_len | Length of sig (256 for RSA-2048). |
| [out] | out | Output payload buffer. |
| [out] | out_len | Bytes written. |
| [in] | cap | Capacity of out. |
Definition at line 723 of file ssh_transport.cpp.
References SSH_MSG_KEXDH_REPLY.
| int ssh_kex_generate | ( | uint8_t | i | ) |
Generate the server ephemeral for the negotiated KEX method (call after parse).
Branches on ssh_sess[i].kex_alg: for diffie-hellman-group14 it delegates to ssh_dh_generate(); for curve25519-sha256 it draws a random X25519 scalar and computes the matching public value into ssh_sess[i].ecdh_sk / ecdh_pk; for ecdh-sha2-nistp256 it draws a random P-256 scalar into ecdh_sk (the 65-byte public point is re-derived when the KEXDH_INIT arrives). Must run after ssh_kexinit_parse() has set kex_alg.
Definition at line 865 of file ssh_transport.cpp.
References SshSession::kex_alg, MAX_SSH_CONNS, ssh_dh_generate(), ssh_ecdsa_p256_pubkey(), SSH_KEX_CURVE25519, SSH_KEX_ECDH_NISTP256, SSH_KEX_MLKEM768_X25519, ssh_rng_fill(), ssh_sess, and ssh_x25519_base().
Referenced by ssh_server_dispatch(), and ssh_transport_begin_rekey().
| int ssh_kexdh_handle | ( | uint8_t | i, |
| const uint8_t * | payload, | ||
| size_t | len, | ||
| uint8_t * | reply_out, | ||
| size_t * | reply_len, | ||
| size_t | cap | ||
| ) |
Handle KEXDH/ECDH_INIT (msg 30) end-to-end and produce the reply payload.
Branches on the negotiated KEX method (ssh_sess[i].kex_alg): computes the shared secret K = e^y mod p (DH-group14) or K = X25519(sk, Q_C) (curve25519), builds the method-correct exchange hash H (e/f as mpints for DH, Q_C/Q_S as strings for curve), signs H with the negotiated host key (rsa-sha2-512/256 or ssh-ed25519), assembles SSH_MSG_KEXDH_REPLY, and derives the six session keys (installed into ssh_keys[i]; encryption is not activated until NEWKEYS - see ssh_newkeys_complete()). On the first KEX the exchange hash is saved as the session id. K is wiped from the stack before returning.
Requires ssh_kex_generate(i) and a host key (ssh_rsa_load_pubkey() and/or ssh_hostkey_ed25519_set()) to have been called.
| [in] | i | SSH slot. |
| [in] | payload | KEXDH_INIT payload. |
| [in] | len | Payload length. |
| [out] | reply_out | KEXDH_REPLY payload buffer. |
| [out] | reply_len | Bytes written to reply_out. |
| [in] | cap | Capacity of reply_out. |
Definition at line 947 of file ssh_transport.cpp.
References bn_dh_validate(), bn_expmod_group14(), bn_from_bytes(), bn_to_bytes(), SshSession::cipher_alg, SshSession::ecdh_pk, SshSession::ecdh_sk, SshSession::have_session_id, SshSession::kex_alg, SshSession::mac_alg, MAX_SSH_CONNS, SshSession::phase, SshSession::session_id, ssh_dh, ssh_dh_derive_keys_sid(), ssh_ecdsa_p256_ecdh(), ssh_ecdsa_p256_pubkey(), SSH_KEX_CURVE25519, SSH_KEX_ECDH_NISTP256, SSH_KEX_MLKEM768_X25519, ssh_kexdh_parse_init(), SSH_PHASE_NEWKEYS, SSH_RSA_PUBKEY_BLOB_MAX, SSH_RSA_SIG_BYTES, ssh_sess, SSH_SHA256_DIGEST_LEN, and ssh_x25519().
Referenced by ssh_server_dispatch().
| void ssh_newkeys_sent | ( | uint8_t | i | ) |
Activate the outbound direction after emitting our SSH_MSG_NEWKEYS.
Call this right after sending the server's NEWKEYS: it turns on the outbound cipher/MAC (enc_out) and starts the s2c compression stream. Per RFC 4253 sec 7.3 each direction activates independently, so the outbound turns on when we send, not when the peer's NEWKEYS arrives.
Definition at line 1093 of file ssh_transport.cpp.
References SshPacketState::enc_out, MAX_SSH_CONNS, and ssh_pkt.
Referenced by ssh_server_dispatch().
| void ssh_newkeys_complete | ( | uint8_t | i | ) |
Complete the NEWKEYS exchange: activate the inbound direction and advance phase.
Called once the client's SSH_MSG_NEWKEYS has been received (the server having already sent its own, via ssh_newkeys_sent()). Turns on the inbound cipher/MAC (enc_in), clears kex_active, and moves to SshPhase::SSH_PHASE_SERVICE (or back to SshPhase::SSH_PHASE_OPEN on a re-key).
Definition at line 1105 of file ssh_transport.cpp.
References SshSession::authed, detws_millis(), SshPacketState::enc_in, SshPacketState::kex_active, SshSession::last_kex_ms, MAX_SSH_CONNS, SshSession::phase, SSH_PHASE_OPEN, SSH_PHASE_SERVICE, ssh_pkt, and ssh_sess.
Referenced by ssh_server_dispatch().
| bool ssh_rekey_needed | ( | uint8_t | i | ) |
True if slot i has reached the re-key threshold (RFC 4253 §9).
Checks both packet sequence numbers against SSH_REKEY_PACKET_THRESHOLD.
Definition at line 1120 of file ssh_transport.cpp.
References MAX_SSH_CONNS, SshPacketState::seq_no_recv, SshPacketState::seq_no_send, ssh_pkt, and SSH_REKEY_PACKET_THRESHOLD.
| bool ssh_rekey_due | ( | uint32_t | seq_send, |
| uint32_t | seq_recv, | ||
| uint32_t | elapsed_ms, | ||
| uint32_t | pkt_threshold, | ||
| uint32_t | time_threshold_ms | ||
| ) |
Pure re-key decision (RFC 4253 §9: "after each gigabyte ... or after each hour").
| seq_send | / |
| seq_recv | the outbound / inbound packet counters (a data-volume proxy). |
| elapsed_ms | milliseconds since the last KEX completed. |
| pkt_threshold | the packet-count trigger (SSH_REKEY_PACKET_THRESHOLD). |
| time_threshold_ms | the elapsed-time trigger (SSH_REKEY_TIME_MS); 0 disables the time trigger. |
Definition at line 1127 of file ssh_transport.cpp.
Referenced by ssh_conn_poll().
| int ssh_transport_begin_rekey | ( | uint8_t | i, |
| uint8_t * | out, | ||
| size_t * | out_len, | ||
| size_t | cap | ||
| ) |
Begin a server-initiated re-key by emitting a fresh KEXINIT.
Generates a new ephemeral DH key pair, builds and stores a new server KEXINIT (I_S), and returns the transport to SshPhase::SSH_PHASE_KEXINIT. The session id and authentication state are preserved, so once the re-key completes the connection resumes in its prior (authenticated) phase.
| [in] | i | Connection slot index. |
| [out] | out | KEXINIT payload to send. |
| [out] | out_len | Bytes written. |
| [in] | cap | Capacity of out. |
Definition at line 1137 of file ssh_transport.cpp.
References MAX_SSH_CONNS, SshSession::phase, ssh_kex_generate(), ssh_kexinit_build(), SSH_PHASE_KEXINIT, and ssh_sess.
Referenced by ssh_conn_poll().
| SshSession ssh_sess[MAX_SSH_CONNS] |
Static pool of SSH session state (BSS), one per SSH slot.
Definition at line 28 of file ssh_transport.cpp.
Referenced by ssh_auth_handle_request(), ssh_conn_close(), ssh_conn_poll(), ssh_conn_rx(), ssh_kex_generate(), ssh_kexdh_handle(), ssh_kexinit_build(), ssh_kexinit_parse(), ssh_newkeys_complete(), ssh_server_dispatch(), ssh_transport_begin_rekey(), ssh_transport_init(), and ssh_transport_recv_banner().