• src/ssh/README.md deucess

    From Deuc¿@VERT to Git commit to main/sbbs/m on Thursday, March 26, 2026 15:33:00
    https://gitlab.synchro.net/main/sbbs/-/commit/f5772748af15bfa26cf7366d
    Modified Files:
    src/ssh/README.md deucessh.h ssh.c
    Log Message:
    Add dssh_cleanse() for secure memory scrubbing

    Public API wrapping OPENSSL_cleanse so applications can scrub
    password buffers without linking OpenSSL themselves. NULL-safe.
    README documents usage and the realloc caveat.

    Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Wednesday, April 01, 2026 20:08:00
    https://gitlab.synchro.net/main/sbbs/-/commit/9578529a90d35395113a5b3d
    Modified Files:
    src/ssh/README.md deucessh-kex.h deucessh-key-algo.h deucessh.h src/ssh/examples/client.c src/ssh/kex/curve25519-sha256.c dh-gex-sha256.c hybrid-pq-kex.c src/ssh/key_algo/rsa-sha2-256-botan.c rsa-sha2-256-openssl.c rsa-sha2-512-botan.c rsa-sha2-512-openssl.c ssh-ed25519-botan.c ssh-ed25519-openssl.c src/ssh/ssh-internal.h ssh-trans.c ssh.c src/ssh/test/dssh_test.h kex_test.c test_alloc.c test_asymmetric_mac.c test_auth.c test_conn.c test_selftest.c test_thread_errors.c test_transport.c test_transport_errors.c
    Log Message:
    Add mandatory host key verification callback for client sessions

    Applications must now set a dssh_hostkey_verify_cb before calling dssh_transport_handshake() on client sessions. The callback receives
    the algorithm name, key strength in bits, SHA-256 fingerprint, and
    raw key blob Ä enabling known_hosts checking and key size policy
    enforcement without requiring the application to parse wire formats.

    New API: dssh_hostkey_decision enum, dssh_hostkey_verify_cb typedef, dssh_session_set_hostkey_verify_cb(), dssh_key_algo_keybits function
    pointer on dssh_key_algo_s. KEX modules invoke the callback after
    exchange hash computation and before signature verification.

    Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Sunday, May 03, 2026 19:11:00
    https://gitlab.synchro.net/main/sbbs/-/commit/57869c54b18727c02e23518a
    Modified Files:
    src/ssh/README.md deucessh.h ssh-internal.h ssh-trans.c ssh.c src/ssh/test/dssh_test_internal.h test_transport.c
    Log Message:
    DeuceSSH: per-session algorithm whitelist filters

    Add five setters that constrain a single session to a subset of
    the globally-registered algorithms, in caller-specified preference
    order, without disturbing the registry's "register once at startup"
    shape:

    dssh_session_set_kex_filter
    dssh_session_set_key_algo_filter
    dssh_session_set_enc_filter
    dssh_session_set_mac_filter
    dssh_session_set_comp_filter

    Motivating case: an app wants aes128-cbc available only on Mystic
    connections, not offered on every other SSH session it makes.
    Register both ciphers globally as before; on the Mystic session
    call dssh_session_set_enc_filter(sess, {"aes128-cbc"}, 1).

    Contract: NULL or count == 0 clears (= use everything registered,
    in registration order). Filter order becomes negotiation preference
    order. Names not registered are silently skipped. Names containing
    ',' return DSSH_ERROR_INVALID. Must be called before
    dssh_session_start(); returns DSSH_ERROR_TOOLATE afterwards.
    Caller-owned input; the library copies the strings.

    Internals: stored as a per-category CSV on the session.
    build_namelist gains a filter parameter Ä when non-NULL it walks
    the filter (in filter order) and emits each name that is actually
    registered, instead of walking the registry. negotiate_algo also
    gains a filter parameter for defense-in-depth so a malformed peer
    list cannot select a filtered name. The server-side host-key
    haskey loop applies the filter alongside its haskey() predicate.

    11 new tests in test_transport.c cover the helper, the build_namelist
    filter logic, the negotiate_algo gate, and every setter rejection
    path (comma, NULL element, empty string, NULL session, TOOLATE
    post-start, replace, clear). 19 existing call sites of build_namelist/negotiate_algo updated to pass NULL.

    OpenSSL: 3410/3410 tests pass. Botan: 3411/3411 tests pass.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Tuesday, May 05, 2026 08:02:00
    https://gitlab.synchro.net/main/sbbs/-/commit/facce791262fb43be1302556
    Modified Files:
    src/ssh/README.md deucessh-conn.h ssh-conn.c ssh-internal.h src/ssh/test/test_chan.c test_conn.c
    Log Message:
    DeuceSSH: rx-window enforcement + DSSH_PARAM_ACCEPT_EARLY_DATA

    Two related changes that together let DeuceSSH clients tolerate
    servers that begin sending CHANNEL_DATA before the terminal-request
    response, while otherwise tightening rx-side window handling.

    1. Universal rx-window truncation.

    handle_channel_data and handle_channel_extended_data now clip the
    inbound payload length to the locally-advertised window before
    invoking the ZC callback. A peer that ignores the window can no
    longer drive the library into unbounded buffering -- bytes past
    the window are silently dropped and never reach the bytebuf. The
    ZC callback contract is widened (and the typedef comment updated)
    to allow len == 0 or len < wire-payload; in-tree consumers
    (stream_zc_cb) already handled both safely.

    2. Per-channel DSSH_PARAM_ACCEPT_EARLY_DATA opt-in (flag 0x02 + new
    dssh_chan_params_set_accept_early_data setter).

    Cryptlib-based servers that don't wait for
    CRYPT_SESSINFO_SSH_CHANNEL_TYPE to be set before sending (such as
    Mystic BBS) emit banner data immediately after CHANNEL_OPEN_
    CONFIRMATION, while local_window is still 0. The flag asks the
    library to deliver pre-setup data anyway: while
    ch->setup_complete is still false, handle_channel_data /
    handle_channel_extended_data take a bypass branch that delivers
    the full dlen, leaves local_window untouched (no credit returned
    to the peer), and skips the ZC WINDOW_ADJUST. Once
    send_window_adjust at the end of dssh_chan_open / dssh_chan_zc_
    open succeeds, setup_complete latches true and the bypass
    disengages permanently.

    Type-locked at chan_open entry: the flag is rejected (NULL
    return) for DSSH_CHAN_SUBSYSTEM since subsystem channels use a
    message queue and have no coherent destination for pre-setup
    bytes.

    init_channel_buffers is hoisted unconditionally to before
    open_session_channel so the bytebufs exist when the first DATA
    arrives. Universal rx-truncation makes this a behavioural no-op
    for non-flagged channels (early data is clipped to len == 0
    before stream_zc_cb runs, so the freshly-allocated bytebuf is
    harmless). The flag is the only thing that punches a hole in
    that, and only while !setup_complete.

    7 new tests:

    - params_set_accept_early_data: setter toggles the bit, NULL guard
    - rx_truncation/default: closed window drops everything
    - rx_truncation/clips_to_window: dlen > local_window clipped to 4
    - rx_bypass/pre_setup: flagged + !setup_complete delivers full
    payload, local_window untouched
    - rx_bypass/disengages_post_setup: flagged + setup_complete drops
    again
    - early_data/type_lock_subsystem: dssh_chan_open returns NULL for
    SUBSYSTEM + ACCEPT_EARLY_DATA

    OpenSSL: 3410/3410 pass. Botan: 3411/3411 pass.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Tuesday, May 05, 2026 08:28:00
    https://gitlab.synchro.net/main/sbbs/-/commit/cd48edf479cad089934c941c
    Modified Files:
    src/ssh/README.md deucessh-conn.h ssh-conn.c src/ssh/test/test_conn.c Log Message:
    DeuceSSH: dssh_chan_poll surfaces terminate as POLLHUP

    When a session is terminated mid-poll, dssh_chan_poll used to return
    0 -- indistinguishable from a timeout -- so a caller in a finite-
    timeout poll loop couldn't tell that the session was gone and would
    keep polling forever.

    Mirror POSIX POLLHUP: when sess->terminate is set, surface every
    requested data flag (READ, READEXT, WRITE) as ready regardless of
    buffer state, just as close_received already does for READ/READEXT.
    The caller's next dssh_chan_read returns 0 (EOF) once any buffered
    data has been drained, and dssh_chan_write returns a negative error,
    so a poll loop exits naturally through the subsequent I/O call.
    DSSH_POLL_EVENT is intentionally not surfaced -- no real event is
    queued, callers should rely on the data flags or the terminate
    callback to detect termination.

    Documented in deucessh-conn.h on the dssh_chan_poll prototype and in
    README.md "Poll events".

    Two new tests in test_conn.c:
    - poll/terminate_surfaces_data: terminate before poll, expect
    READ|READEXT|WRITE returned immediately even with timeout=5000;
    follow-up chan_read returns 0
    - poll/terminate_no_event_bit: poll(EVENT|READ) after terminate
    surfaces only READ, never EVENT

    OpenSSL: 3410/3410 pass. Botan: 3411/3411 pass.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Wednesday, May 06, 2026 12:19:00
    https://gitlab.synchro.net/main/sbbs/-/commit/74ea58b3f5384f92d62eebd4
    Modified Files:
    src/ssh/README.md deucessh-conn.h ssh-conn.c src/ssh/test/test_conn.c Log Message:
    DeuceSSH: non-blocking POSIX semantics for dssh_chan_read/write

    dssh_chan_read previously returned 0 for both empty-buffer and EOF; dssh_chan_write returned 0 for both bufsz==0 and remote-window-full.
    Both now return DSSH_ERROR_NOMORE for the would-block case (analog
    of POSIX -1/EAGAIN), reserving 0 for its POSIX meaning: read=EOF, write=zero-length write.

    Poll-then-{read,write} callers never see NOMORE because dssh_chan_
    poll only flags READ/READEXT/WRITE ready when actual progress is
    possible. Direct callers without a preceding poll now get an
    unambiguous signal instead of a foot-gun.

    Includes test_read_nomore_vs_eof covering empty/peek/data/drained/
    EOF on the read side and bufsz==0/window-full/normal on the write
    side. Two existing tests that hard-coded the old "w==0 means
    window-full" contract are updated; one direct-read test
    (test_data_after_eof) gains a poll() to wait for EOF instead of
    relying on the old "0 means empty or EOF" ambiguity.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Wednesday, May 06, 2026 12:19:00
    https://gitlab.synchro.net/main/sbbs/-/commit/69c190eb44cc60f35407c8fe
    Modified Files:
    src/ssh/README.md deucessh-enc.h deucessh.h src/ssh/enc/aes128-cbc-botan.c aes128-cbc-openssl.c aes256-ctr-botan.c aes256-ctr-openssl.c none.c src/ssh/ssh-trans.c ssh-trans.h ssh.c src/ssh/test/test_selftest.c test_transport.c
    Log Message:
    DeuceSSH: cipher-aware byte rekey + opt-in time rekey

    Two related rekey-policy changes that go together because they share
    the same fix surface (rekey_needed) and rebuild on the same RFC.

    1. Time-based auto-rekey is now off by default and configurable via
    dssh_session_set_rekey_seconds(sess, secs). Pass 0 to disable
    (the new default), DSSH_REKEY_SECONDS for the historical 1-hour
    threshold, or any other positive value. RFC 4253 s9 calls time
    rekey RECOMMENDED, not required, and Cryptlib-based servers
    (Mystic BBS) refuse mid-stream KEXINIT outright with
    CRYPT_ERROR_BADDATA, killing the session at the 1-hour mark.

    2. Byte rekey is now per-cipher per-direction. dssh_enc_s gains a
    bytes_per_key field (third-party-visible ABI bump, agreed); each
    AES module declares 2^36 = 64 GiB (RFC 4344 s3.2: 2^(L/4) blocks
    for L=128), and the none cipher declares UINT64_MAX. rekey_needed
    compares tx_bytes against enc_c2s_selected->bytes_per_key and
    rx_bytes against enc_s2c_selected->bytes_per_key independently --
    no more sum-and-compare against a flat 1 GiB. Pre-handshake
    (NULL ciphers) skips the byte check.

    The DSSH_REKEY_BYTES constant is gone -- the transport gets all byte
    limits from the cipher module, and we don't ship any cipher with
    <128-bit blocks where the legacy 1 GiB fallback would apply.

    Net: AES connections no longer rekey 64x more often than necessary,
    the existing 2^28 packet limit (RFC 4344 s3.1) remains live for
    small-packet sessions, and apps interoperating with brittle peers
    can keep the connection alive past the 1-hour mark.

    Tests: rekey/needed_bytes covers per-direction firing on each side;
    new rekey/bytes_per_direction replaces the old sum-semantics test; rekey/seconds_disabled covers all four states of the new setter;
    selftest seedings use the live cipher's bytes_per_key.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net