9#ifndef PROTOCORE_TRANSPORT_TRANSPORT_H
10#define PROTOCORE_TRANSPORT_TRANSPORT_H
17#include "locus_carcerum/locus_carcerum.h"
174 const uint8_t *h,
size_t h_len);
268 void (*
const recv_ident)(uint8_t *work);
269 void (*
const send_ident)(uint8_t *work);
270 void (*
const kexinit_build)(uint8_t *work);
271 void (*
const kexinit_parse)(uint8_t *work);
272 void (*
const kex_generate)(uint8_t *work);
273 void (*
const exchange_hash)(uint8_t *work);
274 void (*
const kexdh_reply)(uint8_t *work);
275 void (*
const newkeys_sent)(uint8_t *work);
276 void (*
const newkeys_complete)(uint8_t *work);
277 void (*
const rekey_due)(uint8_t *work);
278 void (*
const begin_rekey)(uint8_t *work);
299static const SshTransportNs SshTransport __attribute__((unused)) = {
325#define SSH_TRANSPORT (&SshTransport)
327#if PROTOCORE_SSH_KEX_BENCH
336 volatile long long last_kexgen_us;
337 volatile long long last_kexreply_us;
338 volatile unsigned kex_count;
340extern SshKexBenchCtx protocore_ssh_kex_bench;
344#define SSH_KDF_MAX (4 * PROTOCORE_SHA256_DIGEST_LEN)
466int ssh_pkt_send_at(uint8_t i, uint8_t *wire,
size_t payload_len,
size_t *out_len,
size_t wire_cap,
const SshDir *dir);
500int 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,
511typedef void (*
ssh_msg_handler_t)(uint8_t slot, uint8_t msg_type,
const uint8_t *payload,
size_t payload_len);
544int ssh_pkt_disconnect(uint8_t i, uint32_t reason_code, uint8_t *out,
size_t *out_len,
size_t out_cap,
552#define SSH_UNIMPLEMENTED_LEN 5u
595static inline proto_bool ssh_mac_is_etm(uint8_t mac_mode)
600static inline uint8_t ssh_mac_len(uint8_t mac_mode)
749static inline void ssh_keymat_wipe(uint8_t i)
755 for (uint8_t e = 0; e < 2u; e++)
777 mmgr_zero_buf(km->
aes_key_c2s, PROTOCORE_AES256CTR_KEY_LEN);
778 mmgr_zero_buf(km->
aes_key_s2c, PROTOCORE_AES256CTR_KEY_LEN);
779 mmgr_zero_buf(km->
aes_iv_c2s, PROTOCORE_AES256CTR_CTR_LEN);
780 mmgr_zero_buf(km->
aes_iv_s2c, PROTOCORE_AES256CTR_CTR_LEN);
797static inline void ssh_dh_wipe(uint8_t i)
805 mmgr_zero_buf(
ssh_dh[i].y,
sizeof(protocore_bignum));
806 mmgr_zero_buf(
ssh_dh[i].f,
sizeof(protocore_bignum));
807 mmgr_zero_buf(
ssh_dh[i].K,
sizeof(protocore_bignum));
909 uint32_t sig_len,
const uint8_t *signed_data,
size_t signed_len);
AES-256-CTR stream cipher (aes256-ctr, RFC 4344 §4).
AES-256-GCM AEAD (RFC 5116) - keyed, detached tag.
2048-bit big-integer arithmetic for DH-group14 and RSA-2048.
#define MAX_SSH_CONNS
Maximum simultaneous SSH connections.
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
chacha20-poly1305@openssh.com AEAD cipher (OpenSSH PROTOCOL.chacha20poly1305).
#define PROTOCORE_AESGCM_BORROW
The handshake phase machine, RFC 4253 sec 4.2 through sec 10.
Root infrastructure: fixed widths, serializers, opcodes and sizes, for every layer above.
#define SSH_ECDH_PAIR_LEN
The ECDH ephemeral pair, private then public: what one wipe covers.
SHA-256 (FIPS 180-4) - streaming and one-shot digest.
#define SSH_KEXHASH_MAX_LEN
Longest exchange hash / session_id the two KEX hashes produce (SHA-512).
Ephemeral Diffie-Hellman state for one SSH connection.
protocore_bignum * K
Shared DH secret = e^y mod p (SENSITIVE - wiped after key derivation).
protocore_bignum * f
Server DH public value = g^y mod p (sent to client).
protocore_bignum * y
Server ephemeral private DH scalar (SENSITIVE - wiped after KEX).
One direction's codec state, handed to the packet layer per call.
uint8_t epoch
key epoch it reads out of ssh_keys[slot][]
proto_bool enc
that direction's cipher/MAC is active
const uint8_t * hybrid_sk
RFC 4253 sec 8: every term the exchange hash H is taken over, and where H lands.
const uint8_t * spub
the server public value
size_t cpub_len
its length
const uint8_t * cpub
the client public value
const uint8_t * k_be
the shared secret, big-endian
size_t spub_len
its length
proto_bool k_is_string
that secret is a string, not an mpint
proto_bool is512
the method hashes with SHA-512
size_t hash_len
its length: 32 for the SHA-256 methods, 64 for the SHA-512 one
const uint8_t * ks
the host key blob
proto_bool pub_is_string
the peer public value is a string, not an mpint
AES-256-CTR + HMAC-SHA2-256 session keys for one SSH connection.
uint8_t * chacha_key_s2c
PROTOCORE_CHACHAPOLY_KEY_LEN: server-to-client, used only in chacha mode.
uint8_t * aes_iv_c2s
PROTOCORE_AES256CTR_CTR_LEN: AES IV C→S (CTR counter / GCM nonce); advances per packet.
uint8_t mac_mode_s2c
SSH_MAC_* server-to-client (aes256-ctr only).
proto_bool active
True once keys are installed after successful KEX.
uint8_t * aes_iv_s2c
PROTOCORE_AES256CTR_CTR_LEN: AES IV S→C (CTR counter / GCM nonce); advances per packet.
uint8_t * gcm_ctx_s2c
PROTOCORE_AESGCM_BORROW: keyed GCM context S→C (server seals outbound).
uint8_t * mac_key_c2s
64B: HMAC key, client-to-server (aes mode); 32 bytes for SHA-256, 64 for SHA-512.
uint8_t * aes_key_s2c
PROTOCORE_AES256CTR_KEY_LEN: AES key S→C (server encrypts outbound).
uint8_t cipher_mode_s2c
SSH_CIPHER_* server-to-client.
uint8_t mac_mode_c2s
SSH_MAC_* client-to-server (aes256-ctr only).
uint8_t * gcm_ctx_c2s
PROTOCORE_AESGCM_BORROW: keyed GCM context C→S (server opens inbound).
uint8_t * mac_key_s2c
64B: HMAC key, server-to-client (aes mode).
uint8_t * chacha_key_c2s
PROTOCORE_CHACHAPOLY_KEY_LEN: client-to-server, used only in chacha mode.
uint8_t * aes_key_c2s
PROTOCORE_AES256CTR_KEY_LEN: AES key C→S (server decrypts inbound).
uint8_t cipher_mode_c2s
SSH_CIPHER_* client-to-server.
RFC 4253 sec 6 binary packet: the bytes a receive consumes, and the body it carries.
const uint8_t * payload
a KEXINIT or KEXDH payload
const uint8_t * data
bytes a receive consumes
size_t consumed
bytes a receive took from data
Per-connection SSH binary packet state.
proto_bool tx_ready
A packet is framed and waiting for a worker.
size_t rx_len
Bytes currently in rx_buf.
uint32_t seq_no_recv
Incoming sequence number.
size_t tx_len
Bytes of the framed packet.
uint8_t * mac_work
PROTOCORE_HMAC_SHA256_BORROW bytes. Null until the first packet.
size_t tx_off
Bytes already put on the wire.
uint8_t * rx_buf
SSH_RX_ASM_CAP bytes at SSH_OFF_RX_ASM. Null until claimed.
uint32_t seq_no_send
Outgoing sequence number.
uint8_t * tx_wire
The wire buffer for this slot. Null until the first packet.
RFC 4253 sec 9 key re-exchange: what has passed since the last one, against its budget.
uint32_t pkt_threshold
the volume budget
uint32_t seq_send
packets sent since the last exchange
uint32_t seq_recv
packets received since it
uint32_t time_threshold_ms
the time budget
uint32_t elapsed_ms
time since it
SSH transport/session state for one connection (BSS pool).
uint8_t * ecdh_sk
32B: X25519 scalar / P-256 d, ephemeral private. Wiped by ssh_dh_wipe().
uint8_t mac_alg_c2s
SSH_MAC_* client-to-server (aes cipher only; 0 = hmac-sha2-256).
char * v_s
SSH_VERSION_MAX: server identification string (no CR LF).
uint8_t session_id_len
Session id length (the first KEX's exchange-hash length).
uint16_t v_s_len
Length of v_s.
uint8_t * session_id
SSH_KEXHASH_MAX_LEN: H from the first KEX (RFC 4253 sec 7.2).
proto_bool have_session_id
True once the first KEX completes.
SshDir in
Our inbound direction: encrypted once the peer's arrives.
SshKexAlg kex_alg
negotiated in KEXINIT.
uint8_t * ident_buf
SSH_VERSION_MAX: accumulator for the inbound identification string.
SshHostkeyAlg hostkey_alg
negotiated in KEXINIT.
uint8_t * i_c
PROTOCORE_SSH_I_C_MAX: client KEXINIT payload (for H).
uint16_t i_c_len
Length of i_c.
SshPhase phase
Current handshake phase.
SshDir out
Our outbound direction: encrypted once we sent NEWKEYS, and the epoch it reads.
char * v_c
SSH_VERSION_MAX: client identification string (no CR LF).
uint16_t i_s_len
Length of i_s.
proto_bool authed
True after successful user authentication.
proto_bool kex_active
An exchange is running, from KEXINIT to NEWKEYS (sec 9).
proto_bool kexinit_sent
This end has sent its KEXINIT and not yet its NEWKEYS (sec 7.1).
uint8_t * cpub
PROTOCORE_SSH_CPUB_MAX: exchange value the client sent - e, Q_C or C_INIT (for H).
uint32_t last_kex_ms
protocore_millis() when the last KEX completed.
proto_bool drop_guessed_kex_pkt
The peer guessed a KEX that lost negotiation (sec 7.1).
uint16_t cpub_len
Length of cpub.
uint8_t * ecdh_pk
32B: X25519 ephemeral public (curve25519 KEX only).
proto_bool ext_info_enabled
Peer offered its role's RFC 8308 sec 2.2 indicator.
proto_bool ext_info_sent
EXT_INFO already went out; RFC 8308 sec 2.4 allows it once.
uint8_t * i_s
PROTOCORE_SSH_I_S_MAX: server KEXINIT payload (for H).
uint8_t cipher_alg_s2c
SSH_CIPHER_* server-to-client.
SshPhase phase_before_kex
What to resume when this exchange completes (sec 9).
uint8_t cipher_alg_c2s
SSH_CIPHER_* client-to-server.
uint16_t ident_len
Bytes buffered in ident_buf.
uint8_t mac_alg_s2c
SSH_MAC_* server-to-client (aes cipher only; 0 = hmac-sha2-256).
uint16_t v_c_len
Length of v_c.
void(*const recv_ident)(uint8_t *work)
Where a build or a send writes, and what it wrote.
size_t out_len
what it wrote
uint8_t * out
where a build or a send writes
size_t cap
how much room it has
SshRekeyArgs rekey
sec 9 the volume and time budget since the last exchange
SshPacketArgs pkt
sec 6 the bytes one message occupies
SshTransportOut out_args
where a build or a send writes
SshKexHashArgs kexhash
sec 8 the terms the exchange hash H is taken over
uint8_t slot
the SSH slot a call acts on
uint8_t * protocore_ssh_transport_span(void)
The PROTOCORE_SSH_TRANSPORT_BORROW bytes this module's state lives in.
void protocore_ssh_transport_begin_rekey(uint8_t *work)
SshPacketState ssh_pkt[MAX_SSH_CONNS]
Static packet state pool (BSS). One entry per SSH slot.
SshTransportVars SshTransportV
The operands and the outcome.
int ssh_pkt_emit(uint8_t i, const uint8_t *payload, size_t len, const SshDir *dir)
Frame payload for slot i into the secure pool and raise the flag a worker drains.
proto_bool ssh_pubkey_verify(uint8_t i, const char *pk_algo, const uint8_t *blob, uint32_t blob_len, const uint8_t *sig, uint32_t sig_len, const uint8_t *signed_data, size_t signed_len)
Verify sig over signed_data against the public key in blob, out of slot i's crypto_work....
void protocore_ssh_transport_recv_ident(uint8_t *work)
enum PROTO_ENUM_PACKED SshKexAlg
Negotiated key-exchange method.
const uint8_t * ssh_session_id(uint8_t i, size_t *len)
The session identifier for slot i, or null before the first key exchange completes.
int ssh_pkt_build_disconnect(uint32_t reason_code, const char *desc, size_t desc_len, uint8_t *out, size_t *out_len, size_t cap)
Send DISCONNECT with the no-more-auth-methods reason, then drop.
void protocore_ssh_transport_rekey_due(uint8_t *work)
proto_bool ssh_pubkey_blob_valid(const uint8_t *blob, uint32_t blob_len)
True when blob holds a public key in one of the formats this build decodes.
proto_bool ssh_pubkey_algo_supported(const char *pk_algo, const uint8_t *blob, uint32_t blob_len)
True when pk_algo names an algorithm this end verifies, and blob is of its key type.
enum PROTO_ENUM_PACKED SshHostkeyAlg
Negotiated host-key / signature algorithm.
void protocore_ssh_transport_newkeys_sent(uint8_t *work)
SshKeyMat ssh_keys[MAX_SSH_CONNS][2]
Pool of session key material, two epochs per MAX_SSH_CONNS.
void protocore_ssh_transport_send_ident(uint8_t *work)
void protocore_ssh_transport_kexinit_build(uint8_t *work)
void protocore_ssh_transport_newkeys_complete(uint8_t *work)
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, const SshDir *dir)
Build and send one SSH binary packet.
proto_bool ssh_hostkey_verify(uint8_t i, const uint8_t *ks, size_t ks_len, const uint8_t *sig, size_t sig_len, const uint8_t *h, size_t h_len)
Verify the server's signature over the exchange hash with its host key (RFC 4253 sec 8).
void protocore_ssh_transport_kex_generate(uint8_t *work)
void protocore_ssh_transport_exchange_hash(uint8_t *work)
int ssh_pkt_send_at(uint8_t i, uint8_t *wire, size_t payload_len, size_t *out_len, size_t wire_cap, const SshDir *dir)
Frame the payload_len bytes already written at wire + SSH_WIRE_PAYLOAD_OFF.
int ssh_transport_dispatch(uint8_t i, uint8_t msg_type, const uint8_t *payload, size_t len)
Dispatch one decrypted message; 50 and above go up to the authentication protocol.
proto_bool ssh_kex_prefer_rsa(void)
Current negotiation preference (true = prefer RSA / DH).
void ssh_transport_key_re_exchange(uint8_t i)
Emit a fresh KEXINIT for slot i once its volume or time budget is spent.
int ssh_pkt_recv(uint8_t i, const uint8_t *data, size_t len, ssh_msg_handler_t handler, const SshDir *dir)
Receive and process one or more SSH binary packets from data.
int ssh_pkt_unimplemented(uint8_t i, uint8_t *out, size_t *out_len, size_t out_cap)
Build the SSH_MSG_UNIMPLEMENTED payload answering the packet slot i last received.
void ssh_kex_set_prefer_rsa(proto_bool prefer)
Steer KEX and host-key negotiation toward RSA with DH-group14, or toward curve25519 with ed25519.
void ssh_session_id_latch(uint8_t i, const uint8_t *h, size_t h_len)
Latch the first exchange's hash as slot i's session identifier (RFC 4253 sec 7.2).
@ 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)
int ssh_transport_version_exchange_recv(uint8_t i, const uint8_t *buf, size_t n, size_t *off)
Take the peer identification string off buf (RFC 4253 sec 4.2).
int ssh_pkt_disconnect(uint8_t i, uint32_t reason_code, uint8_t *out, size_t *out_len, size_t out_cap, const SshDir *dir)
Send SSH_MSG_DISCONNECT with reason reason_code.
SshSession ssh_sess[MAX_SSH_CONNS]
Static pool of SSH session state (BSS), one per SSH slot.
@ SSH_HOSTKEY_RSA_SHA512
rsa-sha2-512 (RFC 8332)
@ SSH_KEX_DH_GROUP14
diffie-hellman-group14-sha256 (RFC 8268)
@ SSH_KEX_SNTRUP761_X25519
sntrup761x25519-sha512@openssh.com
@ SSH_KEX_CURVE25519
curve25519-sha256 (RFC 8731)
@ SSH_HOSTKEY_ED25519
ssh-ed25519 (RFC 8709)
@ SSH_KEX_MLKEM768_X25519
mlkem768x25519-sha256
@ SSH_HOSTKEY_RSA_SHA256
rsa-sha2-256 (RFC 8332)
@ SSH_HOSTKEY_ECDSA_NISTP256
ecdsa-sha2-nistp256 (RFC 5656)
@ SSH_KEX_ECDH_NISTP256
ecdh-sha2-nistp256 (RFC 5656 sec 4)
SshDhState ssh_dh[MAX_SSH_CONNS]
Pool of ephemeral DH state, one entry per MAX_SSH_CONNS.
@ SSH_MAC_HMAC_SHA256_ETM
hmac-sha2-256-etm@openssh.com (encrypt-then-MAC)
@ SSH_MAC_HMAC_SHA256
hmac-sha2-256 (encrypt-and-MAC, RFC 4253)
@ SSH_MAC_HMAC_SHA512
hmac-sha2-512 (encrypt-and-MAC)
@ SSH_MAC_HMAC_SHA512_ETM
hmac-sha2-512-etm@openssh.com (encrypt-then-MAC)
void protocore_ssh_transport_kexinit_parse(uint8_t *work)
void ssh_transport_init(uint8_t i)
Bind the session state for SSH connection slot i to the slot's storage.
void ssh_pkt_init(uint8_t i)
Initialize the packet state for SSH connection slot i.
proto_bool ssh_pkt_slot_storage(SshPacketState *s)
Take the slot's one persistent borrow if it has none yet, and split it.
proto_bool ssh_kex_is_sha512(SshKexAlg a)
True when a hashes with SHA-512 rather than SHA-256 (RFC 8268, RFC 8731).
void ssh_pkt_set_client(uint8_t i)
Mark slot i as the SSH client role (call once, right after ssh_pkt_init).
void ssh_kdf_derive(const SshKdfInputs *in, char label, uint8_t *out, size_t out_len)
Derive the RFC 4253 sec 7.2 keys from K, H and the session id into slot i's epoch,...
int ssh_transport_service_request(const uint8_t *payload, size_t len, uint8_t *out, size_t *out_len, size_t cap)
Handle SSH_MSG_SERVICE_REQUEST; emit SERVICE_ACCEPT for ssh-userauth (RFC 4253 sec 10).
proto_bool ssh_kex_shared_secret(const SshKexEphemeral *e, const uint8_t *peer_pub, uint32_t peer_pub_len, uint8_t k_be[256])
Compute K from the peer's exchange value, for the role that sent the first message.
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.
void ssh_kex_install_keys(uint8_t i, const SshKdfInputs *in)
void protocore_ssh_transport_kexdh_reply(uint8_t *work)
int ssh_dh_generate(uint8_t i)
Generate slot i's DH ephemeral: a random y, and f = g^y mod p (RFC 4253 sec 8).
#define PROTO_FALSE
the false value
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
_Bool proto_bool
The truth value.
#define PROTOCORE_END_DECLS