use "../ssh_error"
use "../ssh_crypto"
type SshSessionState is
( SshStateHandshake
| SshStateKeyExchange
| SshStateAuth
| SshStateConnected
| SshStateDisconnected )
class SshStateHandshake
"""Waiting for version exchange."""
var remote_version: (String val | None) = None
class SshStateKeyExchange
"""Key exchange in progress."""
let our_kexinit: Array[U8] val
let their_kexinit: Array[U8] val
let negotiated: SshNegotiatedAlgorithms val
var shared_secret: (Array[U8] val | None) = None
var exchange_hash: (Array[U8] val | None) = None
var awaiting_host_key_verification: Bool = false
// Client-side: set when the server's NEWKEYS has arrived while host-key
// approval is still pending, so accept_host_key() knows to start auth.
var server_newkeys_received: Bool = false
var our_kex: (SshKexCurve25519 | None) = None
// Set once this exchange's ECDH round has been answered, so a repeat of it is
// rejected rather than re-run. On the server each KEX_ECDH_INIT costs an
// X25519 keygen, a derive and a host-key signature; on the client a second
// KEX_ECDH_REPLY would re-derive keys and reset the outgoing sequence number
// (the ChaCha20 nonce) under the key already in use.
var ecdh_completed: Bool = false
new create(our_kexinit': Array[U8] val, their_kexinit': Array[U8] val,
negotiated': SshNegotiatedAlgorithms val)
=>
our_kexinit = our_kexinit'
their_kexinit = their_kexinit'
negotiated = negotiated'
class SshStateAuth
"""Authentication in progress."""
let session_id: Array[U8] val
var methods_remaining: Array[String val] val = recover val Array[String val] end
new create(session_id': Array[U8] val) =>
session_id = session_id'
class SshStateConnected
"""Fully authenticated, channels active."""
let session_id: Array[U8] val
new create(session_id': Array[U8] val) =>
session_id = session_id'
class SshStateDisconnected
"""Terminal state."""
let reason: (SshTransportError | None)
new create(reason': (SshTransportError | None) = None) =>
reason = reason'
class SshRekeyContext
"""
Tracks an in-progress key re-exchange (RFC 4253 §9) while the session stays
Connected. Unlike the initial key exchange this never sets the session id (it
is fixed at the first exchange hash) and returns to Connected — not Auth — at
completion. Each direction's new key is installed at its own NEWKEYS boundary,
tracked by sent_newkeys / recv_newkeys.
"""
let our_kexinit: Array[U8] val
var their_kexinit: (Array[U8] val | None) = None
var negotiated: (SshNegotiatedAlgorithms val | None) = None
var our_kex: (SshKexCurve25519 | None) = None
var derived: (SshDerivedKeys val | None) = None
var sent_newkeys: Bool = false
var recv_newkeys: Bool = false
new create(our_kexinit': Array[U8] val) =>
our_kexinit = our_kexinit'
class SshSessionContext
"""Accumulates connection-lifetime facts."""
var remote_addr: String val = ""
var remote_version: (String val | None) = None
var negotiated_algorithms: (SshNegotiatedAlgorithms val | None) = None
var authenticated_as: (String val | None) = None
var session_id: (Array[U8] val | None) = None
var server_host_key: (SshHostKey val | None) = None
primitive SshRoleClient
primitive SshRoleServer
type SshRole is (SshRoleClient | SshRoleServer)