15#if DETWS_ENABLE_SSH_ZLIB
38static inline uint32_t read_u32_be(
const uint8_t *p)
40 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
43static inline void write_u32_be(uint8_t *p, uint32_t v)
45 p[0] = (uint8_t)(v >> 24);
46 p[1] = (uint8_t)(v >> 16);
47 p[2] = (uint8_t)(v >> 8);
53static size_t compute_padding(
size_t payload_len)
55 size_t total = 5 + payload_len;
56 size_t remainder = total % 16;
57 size_t padding = (remainder == 0) ? 0 : (16 - remainder);
65static void compute_mac_mode(uint8_t mac_mode,
const uint8_t *mac_key, uint32_t seq_no,
const uint8_t *buf,
66 size_t buf_len, uint8_t *mac_out)
69 write_u32_be(seq_be, seq_no);
89static int ct_memcmp(
const uint8_t *a,
const uint8_t *b,
size_t n)
92 for (
size_t i = 0; i < n; i++)
106 memset(s, 0,
sizeof(*s));
116int ssh_pkt_send(uint8_t i,
const uint8_t *payload,
size_t payload_len, uint8_t *out,
size_t *out_len,
size_t out_cap)
127#if DETWS_ENABLE_SSH_ZLIB
133 if (ssh_comp_s2c_active(i))
135 size_t bound = ssh_deflate_bound(payload_len);
138 if (!cbuf || ssh_comp_s2c(i, payload, payload_len, cbuf, bound, &clen) != 0)
158 size_t base = 1 + payload_len;
159 pad_len = 8 - (base % 8);
166 size_t base = 1 + payload_len;
167 pad_len = 16 - (base % 16);
170 tag_len = SSH_AESGCM_TAG_LEN;
174 size_t base = 1 + payload_len;
175 pad_len = 16 - (base % 16);
178 tag_len = ssh_mac_len(km->
mac_mode);
182 pad_len = compute_padding(payload_len);
185 size_t pkt_len = 1 + payload_len + pad_len;
186 size_t wire_len = 4 + pkt_len + tag_len;
188 if (wire_len > out_cap)
192 write_u32_be(out, (uint32_t)pkt_len);
193 out[4] = (uint8_t)pad_len;
194 memcpy(out + 5, payload, payload_len);
195 esp_fill_random(out + 5 + payload_len, pad_len);
220 memcpy(out + 4 + pkt_len, mac, tag_len);
221 ssh_wipe(mac,
sizeof(mac));
270 if (s->
rx_len < wire_need)
273 const size_t scratch_sz = 4 + pkt_len;
286 ssh_wipe(scratch, scratch_sz);
294 ssh_wipe(scratch, scratch_sz);
299 uint8_t pad_len_byte = scratch[4];
300 if (pad_len_byte < 4 || pad_len_byte >= pkt_len)
302 ssh_wipe(scratch, scratch_sz);
305 size_t payload_len = pkt_len - 1 - pad_len_byte;
306 uint8_t msg_type = scratch[5];
307 handler(i, msg_type, scratch + 5, payload_len);
309 size_t consumed = wire_need;
312 ssh_wipe(scratch, scratch_sz);
321 uint32_t pkt_len = read_u32_be(s->
rx_buf);
323 if (pkt_len < 1 || pkt_len >
SSH_PKT_BUF_SIZE - 4 - SSH_AESGCM_TAG_LEN || (pkt_len % 16) != 0)
329 size_t wire_need = 4 + pkt_len + SSH_AESGCM_TAG_LEN;
330 if (s->
rx_len < wire_need)
333 const size_t scratch_sz = pkt_len;
346 ssh_wipe(scratch, scratch_sz);
354 ssh_wipe(scratch, scratch_sz);
359 uint8_t pad_len_byte = scratch[0];
360 if (pad_len_byte < 4 || pad_len_byte >= pkt_len)
362 ssh_wipe(scratch, scratch_sz);
365 size_t payload_len = pkt_len - 1 - pad_len_byte;
366 uint8_t msg_type = scratch[1];
367 handler(i, msg_type, scratch + 1, payload_len);
369 size_t consumed = wire_need;
372 ssh_wipe(scratch, scratch_sz);
380 uint32_t pkt_len = read_u32_be(s->
rx_buf);
381 size_t mac_tag = ssh_mac_len(km->
mac_mode);
389 size_t wire_need = 4 + pkt_len + mac_tag;
390 if (s->
rx_len < wire_need)
393 uint8_t expected_mac[64];
395 if (ct_memcmp(expected_mac, s->
rx_buf + 4 + pkt_len, mac_tag) != 0)
397 ssh_wipe(expected_mac,
sizeof(expected_mac));
402 ssh_wipe(expected_mac,
sizeof(expected_mac));
418 memcpy(scratch, s->
rx_buf + 4, pkt_len);
422 uint8_t pad_len_byte = scratch[0];
423 if (pad_len_byte < 4 || pad_len_byte >= pkt_len)
425 ssh_wipe(scratch, scratch_sz);
428 size_t payload_len = pkt_len - 1 - pad_len_byte;
429 uint8_t msg_type = scratch[1];
430 handler(i, msg_type, scratch + 1, payload_len);
432 size_t consumed = wire_need;
435 ssh_wipe(scratch, scratch_sz);
449 uint8_t saved_counter[16];
450 uint8_t saved_keystream[16];
456 uint8_t len_block[16];
457 memcpy(len_block, s->
rx_buf, 16);
459 uint32_t pkt_len = read_u32_be(len_block);
460 ssh_wipe(len_block,
sizeof(len_block));
466 ssh_wipe(saved_keystream,
sizeof(saved_keystream));
470 size_t enc_len = 4 + pkt_len;
478 size_t mac_tag = ssh_mac_len(km->
mac_mode);
479 size_t wire_need = enc_len + mac_tag;
480 if (s->
rx_len < wire_need)
500 memcpy(scratch, s->
rx_buf, enc_len);
504 const uint8_t *rx_mac = s->
rx_buf + enc_len;
505 uint8_t expected_mac[64];
508 if (ct_memcmp(expected_mac, rx_mac, mac_tag) != 0)
511 ssh_wipe(scratch, scratch_sz);
512 ssh_wipe(expected_mac,
sizeof(expected_mac));
517 ssh_wipe(expected_mac,
sizeof(expected_mac));
522 ssh_wipe(scratch, scratch_sz);
528 uint8_t pad_len_byte = scratch[4];
531 if (pad_len_byte < 4 || pad_len_byte >= pkt_len)
533 ssh_wipe(scratch, scratch_sz);
536 size_t payload_len = pkt_len - 1 - pad_len_byte;
537 uint8_t msg_type = scratch[5];
538 handler(i, msg_type, scratch + 5, payload_len);
541 size_t consumed = wire_need;
544 ssh_wipe(scratch, scratch_sz);
549 uint32_t pkt_len = read_u32_be(s->
rx_buf);
556 size_t wire_need = 4 + pkt_len;
557 if (s->
rx_len < wire_need)
564 uint8_t pad_len_byte = s->
rx_buf[4];
565 if (pad_len_byte >= pkt_len)
567 size_t payload_len = pkt_len - 1 - pad_len_byte;
568 uint8_t msg_type = s->
rx_buf[5];
569 handler(i, msg_type, s->
rx_buf + 5, payload_len);
571 size_t consumed = wire_need;
584int ssh_pkt_disconnect(uint8_t i, uint32_t reason_code, uint8_t *out,
size_t *out_len,
size_t out_cap)
596 payload[1] = (uint8_t)(reason_code >> 24);
597 payload[2] = (uint8_t)(reason_code >> 16);
598 payload[3] = (uint8_t)(reason_code >> 8);
599 payload[4] = (uint8_t)(reason_code);
609 int rc =
ssh_pkt_send(i, payload,
sizeof(payload), out, out_len, out_cap);
#define MAX_SSH_CONNS
Maximum simultaneous SSH connections.
#define SSH_PKT_BUF_SIZE
Packet assembly buffer per SSH connection (bytes).
RAII scope guard for transient scratch borrows.
void * scratch_alloc(size_t n, size_t align)
Borrow n bytes of scratch, aligned to align.
Shared per-dispatch scratch arena (Layer 5, session-scoped memory).
void ssh_aes256ctr_crypt(SshAesCtrCtx *ctx, const uint8_t *in, uint8_t *out, size_t len)
Encrypt or decrypt len bytes in-place (or src→dst).
void ssh_aesgcm_seal(SshAesGcmCtx *ctx, const uint8_t *aad, size_t aad_len, const uint8_t *pt, size_t pt_len, uint8_t *out)
Seal one packet: AES-256-GCM encrypt pt (pt_len bytes) and authenticate it together with aad....
bool ssh_aesgcm_open(SshAesGcmCtx *ctx, const uint8_t *aad, size_t aad_len, const uint8_t *ct, size_t ct_len, const uint8_t tag[SSH_AESGCM_TAG_LEN], uint8_t *out)
Open one packet: verify the 16-byte tag over aad || ct in constant time, and only on success decrypt ...
AES-256-GCM AEAD for SSH (aes256-gcm@openssh.com, RFC 5647).
void ssh_chachapoly_encrypt(const uint8_t key[SSH_CHACHAPOLY_KEY_LEN], uint32_t seqnr, uint8_t *dest, const uint8_t *src, uint32_t payload_len)
Encrypt+authenticate one packet.
uint32_t ssh_chachapoly_get_length(const uint8_t key[SSH_CHACHAPOLY_KEY_LEN], uint32_t seqnr, const uint8_t enc_len[SSH_CHACHAPOLY_AAD_LEN])
Decrypt just the 4-byte length field to learn the packet length before reading the body.
bool ssh_chachapoly_decrypt(const uint8_t key[SSH_CHACHAPOLY_KEY_LEN], uint32_t seqnr, uint8_t *dest, const uint8_t *src, uint32_t payload_len)
Verify+decrypt one packet.
chacha20-poly1305@openssh.com AEAD cipher (OpenSSH PROTOCOL.chacha20poly1305).
#define SSH_CHACHAPOLY_TAG_LEN
Poly1305 tag.
SSH per-connection compression owner (server-to-client zlib / zlib@openssh.com).
void ssh_hmac_sha256_final(SshHmacCtx *ctx, uint8_t mac[SSH_HMAC_SHA256_LEN])
Finalize and write the 32-byte MAC.
void ssh_hmac_sha256_init(SshHmacCtx *ctx, const uint8_t *key, size_t key_len)
Initialize a streaming HMAC-SHA2-256 context.
void ssh_hmac_sha256_update(SshHmacCtx *ctx, const uint8_t *data, size_t len)
Feed len bytes into the running HMAC.
HMAC-SHA2-256 (RFC 2104 + FIPS 198-1).
void ssh_hmac_sha512_final(SshHmacSha512Ctx *ctx, uint8_t mac[SSH_HMAC_SHA512_LEN])
Finish, writing the 64-byte MAC.
void ssh_hmac_sha512_init(SshHmacSha512Ctx *ctx, const uint8_t *key, size_t key_len)
Begin an HMAC-SHA2-512 over key (keys > 128 bytes are pre-hashed per RFC 2104).
void ssh_hmac_sha512_update(SshHmacSha512Ctx *ctx, const uint8_t *data, size_t len)
Feed len message bytes.
HMAC-SHA2-512 (RFC 2104 + FIPS 198-1).
SshKeyMat ssh_keys[MAX_SSH_CONNS]
Pool of session key material, one entry per MAX_SSH_CONNS.
SSH session key material - types, pools, and security model.
@ SSH_CIPHER_CHACHA20POLY1305
chacha20-poly1305@openssh.com (AEAD; no separate MAC)
@ SSH_CIPHER_AES256GCM
aes256-gcm@openssh.com (AEAD, RFC 5647; no separate MAC)
@ SSH_CIPHER_AES256CTR
aes256-ctr + a separate HMAC (the fallback)
@ SSH_MAC_HMAC_SHA512
hmac-sha2-512 (encrypt-and-MAC)
@ SSH_MAC_HMAC_SHA512_ETM
hmac-sha2-512-etm@openssh.com (encrypt-then-MAC)
int ssh_pkt_disconnect(uint8_t i, uint32_t reason_code, uint8_t *out, size_t *out_len, size_t out_cap)
Send SSH_MSG_DISCONNECT with reason reason_code.
SshPacketState ssh_pkt[MAX_SSH_CONNS]
Static packet state pool (BSS). One entry per SSH slot.
int ssh_pkt_send(uint8_t i, const uint8_t *payload, size_t payload_len, uint8_t *out, size_t *out_len, size_t out_cap)
Build and send one SSH binary packet.
void ssh_pkt_init(uint8_t i)
Initialize the packet state for SSH connection slot i.
int ssh_pkt_recv(uint8_t i, const uint8_t *data, size_t len, ssh_msg_handler_t handler)
Receive and process one or more SSH binary packets from data.
SSH binary packet protocol: framing, AES-256-CTR encryption, HMAC-SHA2-256 integrity,...
#define SSH_SEQ_CLOSE_THRESHOLD
Close the connection when seq_no reaches this value.
#define SSH_MSG_DISCONNECT
void(* ssh_msg_handler_t)(uint8_t slot, uint8_t msg_type, const uint8_t *payload, size_t payload_len)
Callback invoked once per complete, verified inbound SSH message.
SSH server-to-client compression: a context-takeover DEFLATE stream (no heap).
uint8_t counter[16]
Current CTR block (big-endian 128-bit counter).
uint8_t pos
Next byte position within keystream[].
uint8_t keystream[16]
Buffered keystream from last AES-ECB call.
Streaming HMAC-SHA2-256 context.
Streaming HMAC-SHA2-512 context (stores the opad key block + inner hash state).
AES-256-CTR + HMAC-SHA2-256 session keys for one SSH connection.
SshAesGcmCtx gcm_s2c
server-to-client AES-256-GCM (server seals outbound with it).
uint8_t mac_key_c2s[64]
HMAC key, client-to-server (aes mode); 32 bytes for SHA-256, 64 for SHA-512.
SshAesCtrCtx c2s_ctx
Client→server cipher (AES-256-CTR); server decrypts inbound with it.
SshAesGcmCtx gcm_c2s
client-to-server AES-256-GCM (server opens inbound with it).
uint8_t cipher_mode
SSH_CIPHER_* selected for this session (0 = aes256-ctr).
uint8_t mac_key_s2c[64]
HMAC key, server-to-client (aes mode).
uint8_t chacha_key_c2s[SSH_CHACHAPOLY_KEY_LEN]
client-to-server, used only in chacha mode.
uint8_t chacha_key_s2c[SSH_CHACHAPOLY_KEY_LEN]
server-to-client, used only in chacha mode.
uint8_t mac_mode
SSH_MAC_* selected for the aes256-ctr cipher (0 = hmac-sha2-256 E&M).
SshAesCtrCtx s2c_ctx
Server→client cipher (AES-256-CTR); server encrypts outbound with it.
Per-connection SSH binary packet state.
size_t rx_len
Bytes currently in rx_buf.
bool enc_out
True once we have sent our NEWKEYS (outbound cipher/MAC active).
uint32_t seq_no_recv
Incoming sequence number (incremented per packet).
uint8_t rx_buf[SSH_PKT_BUF_SIZE]
Raw receive buffer (from transport).
bool enc_in
True once we have received the peer's NEWKEYS (inbound cipher/MAC active).
bool kex_active
True while KEX is in progress (no user data).
uint32_t seq_no_send
Outgoing sequence number (incremented per packet).