• js_socket: resumed client TLS session fails with -43 getting remote ce

    From Rob Swindell@1:103/705 to GitLab issue in main/sbbs on Thu Oct 29 18:37:55 2026
    open https://gitlab.synchro.net/main/sbbs/-/work_items/1311

    ## Summary

    On an **outbound** JS `Socket` TLS connection (`ssl_session = true`), when cryptlib resumes a cached TLS session, `js_socket.cpp` asks for the server's certificate (`CRYPT_SESSINFO_RESPONSE`), gets `CRYPT_ERROR_NOTFOUND` (-43) because a resumed handshake carries no certificate, treats that as a failure, and closes a socket whose TLS session is up and working.

    Reported in DOVE-Net SYNC ("TLS errors?", 2026-10-29; message #57278 on Vertrauen) by poindexter FORTRAN (REALITY, Windows): a flood of

    ```
    TLS ERROR 'Requested item not found in object' (-43) getting remote certificate ```

    several sockets per second, until a reboot clears it, then it comes back. This is probably also the root cause of #1148 (second HTTPS request to the same host fails on Windows, including randy's `letsyncrypt.js --force` report there). #1148 never showed a TLS error because, before 45782f780d (roses-15-films, 2026-10-25), JS socket log output under jsexec on Windows was dropped.

    ## Mechanism (from the bundled cryptlib source, `3rdp/dist/cryptlib.zip`)

    1. After a full client handshake, cryptlib caches the session keyed on `CRYPT_SESSINFO_SERVER_NAME` (`session/tls_hscomplete.c`, `addScoreboardEntryEx()`). We always set that attribute when the script connected by host name.
    2. The next client connection to the same name looks up the cache (`session/tls_cli.c:1147`, `SCOREBOARD_KEY_FQDN`), sends the saved session ID, and if the server accepts, resumes without a certificate exchange.
    3. `getAttributeFunction()` in `session/tls.c` returns `CRYPT_ERROR_NOTFOUND` for `CRYPT_SESSINFO_RESPONSE` when `iKeyexAuthContext == CRYPT_ERROR`, which is the resumed case.
    4. Since the failed connection never removes the cache entry, every later connection to that host resumes and fails the same way until the process restarts.

    This is the TLS 1.2 session-ID path; I could not reproduce it on Linux against gitlab.synchro.net, www.synchro.net or vert.synchro.net (three back-to-back connections each, all full handshakes with a certificate), most likely because those negotiate TLS 1.3. Why it shows up on Windows and not Linux is unverified.

    ## Where it happens

    **Current master** (after 2074253fa1, layout-6-other, 2026-10-28):

    - `js_socket_activate_tls()`: for client mode, after a successful activation, `ret = cryptGetAttribute(session, CRYPT_SESSINFO_RESPONSE, cert)` and that `ret` is returned.
    - The `SOCK_PROP_SSL_SESSION` setter: any non-OK return logs `"setting session active"` and calls `do_js_close()`.

    **Before 2074253fa1** (code from 495ca643bc, vice-11-golf, and 3bd63f8b78, village-20-tire, both 2025-11-09): the same lookup after activation logged `"getting remote certificate"` and fell through to the `ret != CRYPT_OK` cleanup that destroys the session and closes the socket. That is the message in the SYNC report.

    ## Inbound case

    The same -43 is reachable on an **inbound** socket (`ssl_server = true`) only when the script also sets `tls_clientauth`: that turns on `CRYPT_TLSOPTION_MANUAL_CERTCHECK`, and cryptlib's `CRYPT_ENVELOPE_RESOURCE` pause in `session/tls.c` (around line 1110) is not skipped for a resumed session, so the `CRYPT_SESSINFO_RESPONSE` read inside the `CRYPT_ENVELOPE_RESOURCE` branch can also get NOTFOUND. No stock script sets `tls_clientauth` (grep of `exec/`), so this only matters for custom scripts.

    ## Proposed fix (for review)

    Treat `CRYPT_ERROR_NOTFOUND` from `CRYPT_SESSINFO_RESPONSE` as "no peer certificate" rather than an error, in both places in `js_socket_activate_tls()`:

    ```cpp
    static int js_socket_activate_tls(CRYPT_SESSION session, bool server_mode, CRYPT_CERTIFICATE *cert)
    {
    int ret = cryptSetAttribute(session, CRYPT_SESSINFO_ACTIVE, 1);
    if (ret == CRYPT_ENVELOPE_RESOURCE) {
    ret = cryptGetAttribute(session, CRYPT_SESSINFO_RESPONSE, cert);
    if (ret == CRYPT_ERROR_NOTFOUND) {
    /* Resumed session: no certificate was exchanged */
    *cert = -1;
    ret = CRYPT_OK;
    }
    if (ret == CRYPT_OK)
    ret = cryptSetAttribute(session, CRYPT_SESSINFO_AUTHRESPONSE, 1);
    if (ret == CRYPT_OK)
    ret = cryptSetAttribute(session, CRYPT_SESSINFO_ACTIVE, 1);
    }
    if (ret == CRYPT_OK && !server_mode && *cert == -1) {
    ret = cryptGetAttribute(session, CRYPT_SESSINFO_RESPONSE, cert);
    if (ret == CRYPT_ERROR_NOTFOUND) {
    /* Resumed session: no certificate was exchanged */
    *cert = -1;
    ret = CRYPT_OK;
    }
    }
    return ret;
    }
    ```

    With `*cert == -1`, `js_socket_tls_certificate()` already leaves `tls_remote_cert` undefined, so a resumed connection simply has no `tls_remote_cert` property.

    Questions for review:

    - **Inbound + `tls_clientauth`:** is accepting a resumed session without a certificate correct there? The certificate was checked on the original full handshake, but a script that reads `tls_remote_cert` to identify the client would now see it missing on resumed connections. The alternatives are rejecting resumption for client-auth server sockets, or leaving that branch as it is and fixing only the client case.
    - **Client side:** should a resumed session reuse the certificate from the original handshake so `tls_remote_cert` stays populated? That would need our own per-host cache; cryptlib does not keep it.
    - Is there a reason to disable client-side session caching instead (per session, if cryptlib exposes a knob)? That would avoid the question but costs a full handshake on every connection.

    ## Not yet verified

    - A reproduction on Windows (the #1148 repro script should do it, now that 45782f780d makes the TLS error visible under jsexec).
    - Which script or server on the reporter's BBS is making the connections; asked in a SYNC reply (#57279 on Vertrauen).

    -- *Authored by Claude (Claude Code), on behalf of @rswindell*
    --- SBBSecho 3.38-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Deucе@1:103/705 to GitLab note in main/sbbs on Fri Oct 30 08:13:05 2026
    https://gitlab.synchro.net/main/sbbs/-/work_items/1311#note_10776

    Honestly, at first glance, this proposed patch looks terrible... the API is saying "you should validate the server" and we're saying "we've validated nothing and it looks fine to me."

    Really though, DeuceSSH and xptls were written so I don't need to deal with Cryptlib anymore.
    --- SBBSecho 3.38-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)