39static inline uint32_t read_u32_be(
const uint8_t *p)
41 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
44static inline void write_u32_be(uint8_t *p, uint32_t v)
46 p[0] = (uint8_t)(v >> 24);
47 p[1] = (uint8_t)(v >> 16);
48 p[2] = (uint8_t)(v >> 8);
54static size_t compute_padding(
size_t payload_len)
56 size_t total = 5 + payload_len;
57 size_t remainder = total % 16;
58 size_t padding = (remainder == 0) ? 0 : (16 - remainder);
68static void compute_mac_mode(uint8_t mac_mode,
const uint8_t *mac_key, uint32_t seq_no,
const uint8_t *buf,
69 size_t buf_len, uint8_t *mac_out)
72 write_u32_be(seq_be, seq_no);
92static int ct_memcmp(
const uint8_t *a,
const uint8_t *b,
size_t n)
95 for (
size_t i = 0; i < n; i++)
113 memset(s, 0,
sizeof(*s));
133static inline const uint8_t *km_send_chacha(
const SshKeyMat *km,
bool cli)
137static inline const uint8_t *km_recv_chacha(
const SshKeyMat *km,
bool cli)
141static inline const uint8_t *km_send_aes_key(
const SshKeyMat *km,
bool cli)
145static inline uint8_t *km_send_aes_iv(
SshKeyMat *km,
bool cli)
149static inline const uint8_t *km_recv_aes_key(
const SshKeyMat *km,
bool cli)
156static inline pc_aesgcm_key *km_send_gcm(
SshKeyMat *km,
bool cli)
160static inline pc_aesgcm_key *km_recv_gcm(
SshKeyMat *km,
bool cli)
164static inline uint8_t *km_recv_aes_iv(
SshKeyMat *km,
bool cli)
168static inline const uint8_t *km_send_mac(
const SshKeyMat *km,
bool cli)
172static inline const uint8_t *km_recv_mac(
const SshKeyMat *km,
bool cli)
181int 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)
196#if PC_ENABLE_SSH_ZLIB
202 if (ssh_comp_s2c_active(i))
204 size_t bound = ssh_deflate_bound(payload_len);
207 if (!cbuf || ssh_comp_s2c(i, payload, payload_len, cbuf, bound, &clen) != 0)
229 size_t base = 1 + payload_len;
230 pad_len = 8 - (base % 8);
239 size_t base = 1 + payload_len;
240 pad_len = 16 - (base % 16);
249 size_t base = 1 + payload_len;
250 pad_len = 16 - (base % 16);
255 tag_len = ssh_mac_len(km->
mac_mode);
259 pad_len = compute_padding(payload_len);
262 size_t pkt_len = 1 + payload_len + pad_len;
263 size_t wire_len = 4 + pkt_len + tag_len;
265 if (wire_len > out_cap)
271 write_u32_be(out, (uint32_t)pkt_len);
272 out[4] = (uint8_t)pad_len;
273 memcpy(out + 5, payload, payload_len);
274 esp_fill_random(out + 5 + payload_len, pad_len);
287 uint8_t *iv = km_send_aes_iv(km, cli);
288 pc_aesgcm_seal(km_send_gcm(km, cli), iv, out, 4, out + 4, pkt_len, out + 4, out + 4 + pkt_len);
294 pc_aes256ctr_crypt(km_send_aes_key(km, cli), km_send_aes_iv(km, cli), out + 4, out + 4, pkt_len);
295 compute_mac_mode(km->
mac_mode, km_send_mac(km, cli), s->
seq_no_send, out, 4 + pkt_len, out + 4 + pkt_len);
301 compute_mac_mode(km->
mac_mode, km_send_mac(km, cli), s->
seq_no_send, out, 4 + pkt_len, mac);
302 pc_aes256ctr_crypt(km_send_aes_key(km, cli), km_send_aes_iv(km, cli), out, out, 4 + pkt_len);
303 memcpy(out + 4 + pkt_len, mac, tag_len);
304 pc_secure_wipe(mac,
sizeof(mac));
321static int ssh_dispatch_payload(uint8_t i,
const uint8_t *payload,
size_t payload_len,
ssh_msg_handler_t handler)
323#if PC_ENABLE_SSH_ZLIB
324 if (ssh_comp_c2s_active(i))
329 if (!dbuf || ssh_comp_c2s(i, payload, payload_len, dbuf,
SSH_PKT_BUF_SIZE, &dlen) != 0)
337 handler(i, dbuf[0], dbuf, dlen);
341 handler(i, payload[0], payload, payload_len);
355 const uint8_t *rk = km_recv_chacha(km, s->
is_client);
364 if (s->
rx_len < wire_need)
369 const size_t scratch_sz = 4 + pkt_len;
382 pc_secure_wipe(scratch, scratch_sz);
390 pc_secure_wipe(scratch, scratch_sz);
395 uint8_t pad_len_byte = scratch[4];
396 if (pad_len_byte < 4 || pad_len_byte >= pkt_len)
398 pc_secure_wipe(scratch, scratch_sz);
401 size_t payload_len = pkt_len - 1 - pad_len_byte;
402 if (ssh_dispatch_payload(i, scratch + 5, payload_len, handler) < 0)
404 pc_secure_wipe(scratch, scratch_sz);
408 size_t consumed = wire_need;
411 pc_secure_wipe(scratch, scratch_sz);
420 uint32_t pkt_len = read_u32_be(s->
rx_buf);
429 if (s->
rx_len < wire_need)
434 const size_t scratch_sz = pkt_len;
445 uint8_t *iv = km_recv_aes_iv(km, s->
is_client);
447 s->
rx_buf + 4 + pkt_len, scratch))
449 pc_secure_wipe(scratch, scratch_sz);
458 pc_secure_wipe(scratch, scratch_sz);
463 uint8_t pad_len_byte = scratch[0];
464 if (pad_len_byte < 4 || pad_len_byte >= pkt_len)
466 pc_secure_wipe(scratch, scratch_sz);
469 size_t payload_len = pkt_len - 1 - pad_len_byte;
470 if (ssh_dispatch_payload(i, scratch + 1, payload_len, handler) < 0)
472 pc_secure_wipe(scratch, scratch_sz);
476 size_t consumed = wire_need;
479 pc_secure_wipe(scratch, scratch_sz);
487 uint32_t pkt_len = read_u32_be(s->
rx_buf);
488 size_t mac_tag = ssh_mac_len(km->
mac_mode);
496 size_t wire_need = 4 + pkt_len + mac_tag;
497 if (s->
rx_len < wire_need)
502 uint8_t expected_mac[64];
504 if (ct_memcmp(expected_mac, s->
rx_buf + 4 + pkt_len, mac_tag) != 0)
506 pc_secure_wipe(expected_mac,
sizeof(expected_mac));
511 pc_secure_wipe(expected_mac,
sizeof(expected_mac));
529 memcpy(scratch, s->
rx_buf + 4, pkt_len);
533 uint8_t pad_len_byte = scratch[0];
534 if (pad_len_byte < 4 || pad_len_byte >= pkt_len)
536 pc_secure_wipe(scratch, scratch_sz);
539 size_t payload_len = pkt_len - 1 - pad_len_byte;
540 if (ssh_dispatch_payload(i, scratch + 1, payload_len, handler) < 0)
542 pc_secure_wipe(scratch, scratch_sz);
546 size_t consumed = wire_need;
549 pc_secure_wipe(scratch, scratch_sz);
564 const uint8_t *rk = km_recv_aes_key(km, s->
is_client);
565 uint8_t *rctr = km_recv_aes_iv(km, s->
is_client);
570 size_t enc_len = 4 + pkt_len;
578 size_t mac_tag = ssh_mac_len(km->
mac_mode);
579 size_t wire_need = enc_len + mac_tag;
580 if (s->
rx_len < wire_need)
602 memcpy(scratch, s->
rx_buf, enc_len);
606 const uint8_t *rx_mac = s->
rx_buf + enc_len;
607 uint8_t expected_mac[64];
610 if (ct_memcmp(expected_mac, rx_mac, mac_tag) != 0)
613 pc_secure_wipe(scratch, scratch_sz);
614 pc_secure_wipe(expected_mac,
sizeof(expected_mac));
619 pc_secure_wipe(expected_mac,
sizeof(expected_mac));
624 pc_secure_wipe(scratch, scratch_sz);
630 uint8_t pad_len_byte = scratch[4];
633 if (pad_len_byte < 4 || pad_len_byte >= pkt_len)
635 pc_secure_wipe(scratch, scratch_sz);
638 size_t payload_len = pkt_len - 1 - pad_len_byte;
639 if (ssh_dispatch_payload(i, scratch + 5, payload_len, handler) < 0)
641 pc_secure_wipe(scratch, scratch_sz);
646 size_t consumed = wire_need;
649 pc_secure_wipe(scratch, scratch_sz);
657 uint32_t pkt_len = read_u32_be(s->
rx_buf);
664 size_t wire_need = 4 + pkt_len;
665 if (s->
rx_len < wire_need)
676 uint8_t pad_len_byte = s->
rx_buf[4];
677 if (pad_len_byte >= pkt_len)
681 size_t payload_len = pkt_len - 1 - pad_len_byte;
682 if (ssh_dispatch_payload(i, s->
rx_buf + 5, payload_len, handler) < 0)
687 size_t consumed = wire_need;
717 size_t take = len < space ? len : space;
729 r = ssh_recv_chachapoly(i, s, km, handler);
733 r = ssh_recv_aesgcm(i, s, km, handler);
737 r = ssh_recv_ctr_etm(i, s, km, handler);
741 r = ssh_recv_ctr_emac(i, s, km, handler);
745 r = ssh_recv_plain(i, s, km, handler);
766int ssh_pkt_disconnect(uint8_t i, uint32_t reason_code, uint8_t *out,
size_t *out_len,
size_t out_cap)
780 payload[1] = (uint8_t)(reason_code >> 24);
781 payload[2] = (uint8_t)(reason_code >> 16);
782 payload[3] = (uint8_t)(reason_code >> 8);
783 payload[4] = (uint8_t)(reason_code);
793 int rc =
ssh_pkt_send(i, payload,
sizeof(payload), out, out_len, out_cap);
void pc_aes256ctr_crypt(const uint8_t key[PC_AES256CTR_KEY_LEN], uint8_t counter[PC_AES256CTR_CTR_LEN], const uint8_t *in, uint8_t *out, size_t len)
Encrypt or decrypt len bytes with AES-256-CTR (the two are identical).
uint32_t pc_aes256ctr_get_length(const uint8_t key[PC_AES256CTR_KEY_LEN], const uint8_t counter[PC_AES256CTR_CTR_LEN], const uint8_t enc4[4])
Decrypt only the 4-byte SSH packet_length prefix WITHOUT advancing counter.
PC_CRYPTO_HOT void pc_aesgcm_iv_increment(uint8_t iv[PC_AESGCM_IV_LEN])
Advance the RFC 5647 invocation counter: the low 8 bytes of the 12-byte nonce as a big-endian integer...
AES-256-GCM AEAD (RFC 5116) - stateless, detached-tag API.
#define PC_AESGCM_TAG_LEN
GCM authentication tag length (bytes).
bool pc_chachapoly_decrypt(const uint8_t key[PC_CHACHAPOLY_KEY_LEN], uint32_t seqnr, uint8_t *dest, const uint8_t *src, uint32_t payload_len)
Verify+decrypt one packet.
uint32_t pc_chachapoly_get_length(const uint8_t key[PC_CHACHAPOLY_KEY_LEN], uint32_t seqnr, const uint8_t enc_len[PC_CHACHAPOLY_AAD_LEN])
Decrypt just the 4-byte length field to learn the packet length before reading the body.
void pc_chachapoly_encrypt(const uint8_t key[PC_CHACHAPOLY_KEY_LEN], uint32_t seqnr, uint8_t *dest, const uint8_t *src, uint32_t payload_len)
Encrypt+authenticate one packet.
chacha20-poly1305@openssh.com AEAD cipher (OpenSSH PROTOCOL.chacha20poly1305).
#define PC_CHACHAPOLY_TAG_LEN
Poly1305 tag.
RAII scope guard for transient scratch borrows.
void pc_hmac_sha256_update(pc_hmac_sha256_ctx *ctx, const uint8_t *data, size_t len)
Feed len bytes into the running HMAC.
void pc_hmac_sha256_init(pc_hmac_sha256_ctx *ctx, const uint8_t *key, size_t key_len)
Initialize a streaming HMAC-SHA2-256 context.
void pc_hmac_sha256_final(pc_hmac_sha256_ctx *ctx, uint8_t mac[PC_HMAC_SHA256_LEN])
Finalize and write the 32-byte MAC.
HMAC-SHA2-256 (RFC 2104 + FIPS 198-1) - streaming context and one-shot API.
void pc_hmac_sha512_final(pc_hmac_sha512_ctx *ctx, uint8_t mac[PC_HMAC_SHA512_LEN])
Finish, writing the 64-byte MAC.
void pc_hmac_sha512_init(pc_hmac_sha512_ctx *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 pc_hmac_sha512_update(pc_hmac_sha512_ctx *ctx, const uint8_t *data, size_t len)
Feed len message bytes.
HMAC-SHA2-512 (RFC 2104 + FIPS 198-1) - streaming context and one-shot API.
bool pc_aesgcm_open(pc_aesgcm_key *k, const uint8_t nonce[PC_AESGCM_IV_LEN], const uint8_t *aad, size_t aad_len, const uint8_t *ct, size_t ct_len, const uint8_t tag[PC_AESGCM_TAG_LEN], uint8_t *out)
Open one record: verify tag over aad || ct in constant time, then (only on success) decrypt ct into o...
pc_cspan pc_aesgcm_seal(pc_aesgcm_key *k, const uint8_t nonce[PC_AESGCM_IV_LEN], const uint8_t *aad, size_t aad_len, const uint8_t *pt, size_t pt_len, uint8_t *ct_out, uint8_t tag_out[PC_AESGCM_TAG_LEN])
Seal one record under k and nonce.
#define SSH_PKT_BUF_SIZE
Packet assembly buffer per SSH connection (bytes).
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).
Secure pool accessor - borrows that hold key material.
SSH per-connection compression owner (server-to-client zlib / zlib@openssh.com).
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.
void ssh_pkt_set_client(uint8_t i)
Mark slot i as the SSH client role (call once, right after ssh_pkt_init).
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).
AES-256-CTR + HMAC-SHA2-256 session keys for one SSH connection.
uint8_t aes_iv_c2s[PC_AES256CTR_CTR_LEN]
AES IV C→S (CTR counter / GCM nonce); advances per packet.
uint8_t chacha_key_s2c[PC_CHACHAPOLY_KEY_LEN]
server-to-client, used only in chacha mode.
uint8_t aes_iv_s2c[PC_AES256CTR_CTR_LEN]
AES IV S→C (CTR counter / GCM nonce); advances per packet.
uint8_t mac_key_c2s[64]
HMAC key, client-to-server (aes mode); 32 bytes for SHA-256, 64 for SHA-512.
uint8_t gcm_ctx_s2c[PC_WORK_AESGCM]
keyed GCM context S→C (server seals outbound).
uint8_t aes_key_c2s[PC_AES256CTR_KEY_LEN]
AES key C→S (server decrypts inbound).
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[PC_CHACHAPOLY_KEY_LEN]
client-to-server, used only in chacha mode.
uint8_t mac_mode
SSH_MAC_* selected for the aes256-ctr cipher (0 = hmac-sha2-256 E&M).
uint8_t aes_key_s2c[PC_AES256CTR_KEY_LEN]
AES key S→C (server encrypts outbound).
uint8_t gcm_ctx_c2s[PC_WORK_AESGCM]
keyed GCM context C→S (server opens inbound).
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).
Streaming HMAC-SHA2-256 context.
Streaming HMAC-SHA2-512 context (stores the opad key block + inner hash state).