Skip to content

Eval mbedtls: add custom BIO + own cert + ALPN bindings for libuv integration - #12986

Open
kevinresol wants to merge 2 commits into
HaxeFoundation:developmentfrom
kevinresol:eval-mbedtls
Open

Eval mbedtls: add custom BIO + own cert + ALPN bindings for libuv integration#12986
kevinresol wants to merge 2 commits into
HaxeFoundation:developmentfrom
kevinresol:eval-mbedtls

Conversation

@kevinresol

@kevinresol kevinresol commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

I was trying to make use of the eval.luv package to run a TLS TCP server. But found that several needed mbedtls API are not exposed. The entire code change together with the summary below is AI-generated and obviously I have no idea what I am doing especially on the ocaml part. So please review carefully.

I tried the changes locally and apparently I successfully ran a secure TCP server/client on eval+luv

Description

What changed

This PR extends eval’s mbedtls.* surface to support TLS over non-socket transports (needed when building TLS on eval.luv.Tcp) and closes several binding gaps.

  • Added mbedtls.Config.own_cert(cert, pk) across Haxe/OCaml/C bindings.
  • Added ALPN support:
    • mbedtls.Config.alpn_protocols(protocols:Array<String>)
    • mbedtls.Ssl.get_alpn_protocol():Null<String>
  • Added public custom BIO API:
    • mbedtls.Ssl.set_bio(send, recv) with (Bytes, pos, len) -> Int callbacks.
  • Added SSL error constants in mbedtls.Error:
    • WANT_READ, WANT_WRITE, PEER_CLOSE_NOTIFY.
  • Updated eval runtime wiring (evalSsl.ml) for all new APIs.

Native/runtime changes (libs/mbedtls/mbedtls_stubs.c)

  • Reworked SSL wrapper to own and clean up BIO callback roots correctly (fixes leak from repeated set_bio usage).
  • Reworked config wrapper to own ALPN protocol list memory for correct lifetime and cleanup.
  • Added C stubs for:
    • ml_mbedtls_ssl_conf_own_cert
    • ml_mbedtls_ssl_conf_alpn_protocols
    • ml_mbedtls_ssl_get_alpn_protocol
    • hx_get_ssl_error_flags
  • Updated BIO callback bridge to pass (bytes, pos, len) rather than the previous string-only callback shape.
  • Added mbedtls v4 compatibility paths for mbedtls_pk_parse_key* signatures.

Eval std extern updates

  • std/eval/_std/mbedtls/Config.hx: own_cert, alpn_protocols
  • std/eval/_std/mbedtls/Ssl.hx: set_bio, get_alpn_protocol
  • std/eval/_std/mbedtls/Error.hx: WANT_READ, WANT_WRITE, PEER_CLOSE_NOTIFY
  • src/macro/eval/evalSsl.ml: wiring for all of the above.

Tests

Added new eval project tests/misc/eval/projects/TlsBio/ with checked-in PEM fixtures and --interp coverage for:

  • custom BIO handshake/data flow
  • own_cert server setup
  • ALPN negotiation
  • mTLS client cert flow

Build robustness

  • libs/mbedtls/dune now accepts optional HAXE_MBEDTLS_CFLAGS include flags for local environments with non-default mbedtls header locations. (Reverted as it is breaking linux build)

Why

This gives eval the same TLS primitives required by libuv integration (cert/key, ca, alpn, custom transport pump with WANT_*) without relying on sys.ssl.Socket.

Validation

  • tests/misc/eval/projects/TlsBio/compile.hxml passes (Main.hx:79: ok).
  • Existing eval mbedtls constant smoke test (Issue11631) still passes.
  • Rebuilding via make haxe now produces a fresh runnable ./haxe binary reliably.

…tion

Expose eval mbedtls primitives needed for libuv-driven TLS transports by adding set_bio, own_cert, ALPN bindings, and WANT_* error constants across Haxe/OCaml/C layers. Also fix BIO/config lifetime management in native stubs, add eval TLS coverage under tests/misc/eval/projects/TlsBio.
kevinresol added a commit to kevinresol/tink_tcp that referenced this pull request Jul 9, 2026
kevinresol added a commit to haxetink/tink_tcp that referenced this pull request Jul 27, 2026
* Implement node client

* More asserts

* Implement java

* Update lib

* Cleanup

* Fix lib

* Fill some of the gaps

* Modernize haxe syntax

* Support Eval via luv

* Simply use the default event loop on eval

* Bind server to host:port

* Impl TLS

* Cleanup

* Initial TLS support

* cleanup

* TLS support on eval

requires HaxeFoundation/haxe#12986

* cleanup

* HL support (kind of)

still have to smooth the linking (for bytecode mode and HL/C)

* cpp support via linc_uv

* DRY

* Refactor a bit

* wip

* Refactor

* Simplify

* Refactor

* Cleanup jvm

* Reshape public API for T1: Handler/IncomingConnection, static Client.connect, and Server.bind with endpoint/shutdown.

Remove the old Connection surface so targets and docs can follow the new types.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add shared Session.run so platform clients/servers no longer inline pipeTo.

Centralizes handler invoke + pipeTo({end: true}) fire-and-forget plumbing in one place (T2).

Co-authored-by: Cursor <cursoragent@cursor.com>

* Harden Node Client/Server for T3: private duplex/server ctors and audited dial/accept/TLS Session.run semantics.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Rewrite core tests for the Handler-only bind/connect API.

Drop TestAccept and old client/#if construction so the suite matches dial-vs-stream semantics and server.endpoint/shutdown.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Harden JVM Client/Server for T5: private duplex/server ctors, dial Promise with Session.run, and shutdown-safe accept.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Harden Eval Client/Server for T6: private duplex, dial/Session.run, and EvalLoop keep-alive.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Harden HashLink Client/Server for T7: private duplex, dial/Session.run, and BindOptions.loop.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Harden C++ Client/Server for T8: private duplex, dial/Session.run, and shutdown-safe accept.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Rewrite TLS tests for the Handler bind/connect API and options.tls.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Complete T10 docs and final purge: Handler-only README, Connection→Duplex rename, and drop NodeTestNative.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Minor

* Don't worry about cpp for now

* Remap files so it works for haxe 4 and 5

* Migrate eval LuvLoopWrapper onto EventLoopDriver via swapDriver.

Restores compile against shadowed EventLoop (no nativeLoop) and matches HL wait/close/isDefault semantics.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Restore sibling linc_uv wiring for cpp EventLoopDriver verification.

Without -lib linc_uv the cpp suite never loads UvEventLoopDriver; V1 confirms interp 6/6 and cpp 7/7.

Co-authored-by: Cursor <cursoragent@cursor.com>

* deps

* Promote IncomingConnection to an interface with optional Session.abort wiring.

Handlers need a stable abort() entry point before platforms hard-close sockets; Session.run keeps compiling with a default no-op thunk.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Node duplex abort via socket destroy for Session handlers.

Retain the native Socket so IncomingConnection.abort can hard-close live Node sessions without resetAndDestroy.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Java plain and TLS abort through session force-close.

Handlers can hard-close AsynchronousSocketChannel sessions without SO_LINGER or orderly TLS shutdown races.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire HashLink duplex abort best-effort without UV shutdown.

Retain streams/sessions so IncomingConnection.abort can fail waiters and hard-close despite the known HL toolchain skew.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire eval duplex abort and guard TLS writes after hard-close.

Fail pending waiters and skip UV shutdown on abort so Session teardown cannot hang in writeBytes WANT_READ loops.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire cpp duplex abort and fail TLS writes after force-close.

Skip UV shutdown on abort and guard writeBytes so mid-session hard-close cannot spin on WANT_READ.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Document IncomingConnection.abort and add mid-session abort tests.

Prove handlers can hard-close without graceful FIN, without promising RST, across interp/jvm/cpp.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Propagate sink end and TLS shutdown failures into pipeTo Futures.

Fold optional end/shutdown into DuplexSink, TlsSink, and JavaTlsSink consume so half-close failures are visible and body errors are not overwritten.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Expose per-session SessionOutcome via IncomingConnection.closed.

Fulfill closed once from pipe mapping or abort-before-thunk so handlers can observe graceful, aborted, or failed outbound completion without changing dial/listen Promises.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add Server.errors Signal with Java accept and TLS emits.

Expose listen-adjacent faults on ServerObject; stub non-Java platforms and re-arm Java accept after non-shutdown failures.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Document and test IncomingConnection.closed SessionOutcome.

Cover graceful, abort, and outbound-failure settlement so handlers can rely on closed without changing dial semantics.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire EvalServer.errors for listen-adjacent accept and TLS faults.

Emit non-shutdown listen/accept failures and handshake errors so interp servers surface faults that never reach Handler.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire NodeServer.errors for post-bind and TLS client faults.

Forward server error and tlsClientError after listen success without double-emitting bind-time Promise failures.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire CppServer.errors for non-shutdown accept and TLS faults.

Surface listen-adjacent UV and handshake failures while silencing shutdown cancel noise.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Best-effort HlServer.errors for accept and TLS handshake faults.

Replace the stub Signal with shutdown-aware emits matching Eval/Cpp spirit without requiring HL toolchain success.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Document and test Server.errors for listen-adjacent faults.

Assert TLS handshake failures emit on errors without invoking Handler, keeping session I/O on closed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Collapse Session into Handler.run with a shared Connection interface.

Locks the callable Handler abstraction and colocated SessionOutcome so platforms can call app.run(duplex) next; Session modules are removed to avoid type collisions.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Migrate Eval duplexes to Connection and app.run for first Handler.run proof.

Interp suite (including session-closed and abort tests) now exercises the shared Connection path before other platforms migrate.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Migrate Node duplexes to Connection and app.run.

Aligns the Node client/server path with Handler.run so dial/accept no longer unpack Session.run arguments.

* Migrate HL duplexes to Connection and app.run (best-effort).

Completes the HL static migration so Handler.run owns session lifecycle without requiring an HL toolchain gate.

* Migrate Java duplexes to Connection and app.run.

Preserves dial Promise fulfillment after Handler.run so JVM connect semantics stay intact.

* Finish Cpp Connection duplexes and migrate to app.run.

Makes Cpp duplex interface members public and routes dial/accept through Handler.run.

* Align README with Handler.run and Connection after Session collapse.

Documents the public typedef IncomingConnection and app.run(duplex) call shape now that platform migrations are complete.

* libs

* install linc_uv via git

so that submodules are also pulled

* Rename NodeDuplex to NodeConnection.

Aligns plain Node TCP plumbing with the Connection naming used by the shared TLS path; no behavior change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Expand TlsSession with abort and endpoint getters.

Enables a shared TlsConnection by moving endpoint/abort onto the session interface; mock updated so tests compile. Held off api2 until T2–T5 land with it.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Implement JavaTlsSession endpoints and main-thread callbacks.

Marshals TLS user callbacks onto OnMainThread and exposes channel endpoints so generic TlsSource/TlsSink can replace Java-specific IO.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Move EvalTlsSession endpoint getters from EvalTlsDuplex.

Keeps sock/peer name fallbacks so TlsConnection can read endpoints from the session alone.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Expose CppTlsSession endpoints from the live TCP handle.

Drops duplex ctor overrides in favor of sock/peer addresses for the shared TlsConnection path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Store optional endpoints on HlTlsSession with safe defaults.

Lets callers pass local/peer into the session later without breaking existing two-arg construction.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add platform-agnostic TlsConnection over TlsSession.

Single Connection wrapper using shared TLS source/sink so platform *TlsDuplex types can be deleted.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Java TLS through TlsConnection; drop Java TLS duplex IO.

Removes JavaTlsDuplex/Source/Sink now that JavaTlsSession marshals callbacks for the shared TLS path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Eval TLS through TlsConnection; delete EvalTlsDuplex.

Keeps eval_tls gating while routing handshake success into the shared TlsConnection.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Cpp TLS through TlsConnection; drop endpoint overrides.

TlsConnection now takes local/peer only from CppTlsSession live-handle getters.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Hl TLS through TlsConnection; pass endpoints into session.

Moves former HlTlsDuplex local/peer args onto HlTlsSession and deletes the duplex.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Rename JavaDuplex to JavaConnection.

Aligns plain Java TCP plumbing with Connection naming; TLS path unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Rename EvalDuplex to EvalConnection.

Aligns plain Eval TCP plumbing with Connection naming; TLS path unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Rename HlDuplex to HlConnection.

Aligns plain HL TCP plumbing with Connection naming; TLS path unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Rename CppDuplex to CppConnection.

Aligns plain Cpp TCP plumbing with Connection naming; TLS path unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Update README API sketch to Connection naming.

Matches final *Connection / TlsConnection wiring after the duplex collapse.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix nodejs outcome

* Add TcpSession interface over DuplexStream.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add platform-agnostic TcpConnection over TcpSession.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Make WrappedStream implement TcpSession with live endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Make HlUvStream implement TcpSession with optional endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Make CppUvStream implement TcpSession with live endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Eval plain TCP through TcpConnection; drop EvalConnection.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Cpp plain TCP through TcpConnection; drop CppConnection.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Hl plain TCP through TcpConnection; drop HlConnection.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Rewrite TlsSession to extend TcpSession with handshake only.

Private LG1 branch — do not land alone on api2.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Convert JavaTlsSession I/O to Promise DuplexStream shape.

Private LG1 branch — do not land alone on api2.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Convert EvalTlsSession I/O to Promise DuplexStream shape.

Private LG1 branch — do not land alone on api2.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Convert CppTlsSession I/O to Promise DuplexStream shape.

Private LG1 branch — do not land alone on api2.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Convert HlTlsSession I/O to Promise DuplexStream shape.

Private LG1 branch — do not land alone on api2.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Retarget TlsConnection to Duplex wrappers; delete TlsSource/TlsSink.

Private LG1 branch — land with full S5–S9 unit only.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Java TLS through TcpConnection after handshake.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Eval TLS through TcpConnection after handshake.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Hl TLS through TcpConnection after handshake.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Wire Cpp TLS through TcpConnection after handshake.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Remove temporary TlsConnection after TcpConnection wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Document TcpConnection as the shared UV+TLS connection type.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Move shared duplex/session types into tink.tcp.internal.

Keeps platform wrappers under tink.io for now while retargeting implements and call sites to the new shared package.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Move JavaTlsSession into tink.tcp.internal.java.

Adds the required OnMainThread import after leaving tink.io.java and retargets Java client/server call sites.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Move Eval TCP/TLS sessions into tink.tcp.internal.eval.

Renames WrappedStream to EvalTcpSession, drops luv/, and retargets Eval client/server call sites.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Move Cpp UV/TLS sessions into tink.tcp.internal.cpp.

Renames CppUvStream to CppTcpSession and retargets Cpp client/server call sites.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Move Hl UV/TLS sessions into tink.tcp.internal.hl.

Renames HlUvStream to HlTcpSession and retargets Hl client/server call sites.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Move eval loop into internal.eval and retarget clients.

Keeps platform eval wiring under tink.tcp.internal so public tcp packages stay free of target-private types.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Move cpp mbedtls bindings into internal.cpp and retarget consumers.

Keeps target-private cpp TLS types under tink.tcp.internal so public packages no longer depend on tink.tcp.cpp.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Move HL loop and UvExtras into internal.hl and retarget consumers.

Keeps HashLink-private types under tink.tcp.internal so public clients/servers no longer import tink.tcp.hl.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Compile hxcpp tools

* ci

* ci

* fix tests

* Fix Linux cpp UV segfaults from SIGPIPE and unrooted setData.

Ignore SIGPIPE, retain sessions/connect/write contexts as GC roots, reject writes after close, and avoid hxcpp PointerReference dangling from chained UV abstracts.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Include signal.h so SIGPIPE ignore compiles on Linux.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant