Module crypto/tls

crypto/tls
Stability: unstable — may still change; see below.

TLS client streams over OpenSSL — plans/archive/D6_TLS_PLAN.md (the O2 decision: one TlsStream implementing the D5 Reader/Writer traits).

On unix targets this requires OpenSSL 1.1.1+/3.x at BUILD time: the compiler probes pkg-config openssl (with Homebrew keg fallbacks) and adds the include and link flags; without it, importing this module fails at the C step with the linker naming the missing library. Windows needs no OpenSSL — it gets a Schannel/SSPI backend behind the same fifteen __yo_tls_* functions (shipped in v0.2.26, plans/archive/D6_TLS_PLAN.md). On wasm there is no backend and tls_available() answers false.

Integration model: the TCP socket belongs to Yo's per-thread async runtime, so OpenSSL never sees the fd. The SSL object speaks through two MEMORY BIOs; this module pumps ciphertext between them and the TcpStream around every handshake/read/write step.

{ TlsStream } :: import("std/crypto/tls");
s := io.await(TlsStream.connect(`example.com`.to_string(), u16(443), io), e);
_n := io.await(s.write_str("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", io), e);
n := io.await(s.read(buf.ptr().unwrap(), buf.capacity(), io), e);
io.await(s.close(io), e);

Stability

unstable — the transport works on every supported target (D6 closed 2026-09-04; the compiler's own downloader runs over it) and both backends verify the chain against the system trust store and check the hostname, so what is unstable is how narrow the surface is. There is exactly one entry point, TlsStream.connect(host, port, io), and no way to say anything else: no options value, so no custom CA bundle, no client certificate, no ALPN (which HTTP/2 would need), no session resumption, no "accept this self-signed certificate for a test", and no server side. Every one of those arrives as a new parameter or a new options type, which is why connect's signature is not frozen. Certificate REVOCATION is deliberately not checked on either backend, matching them to each other rather than to a policy. Freezing follows the first of those features, not a release count.

Types

TlsError enum
TlsError

TLS failures, D1 style. Handshake/Io carry OpenSSL's own message.

Variants

VariantFieldsDescription
Connecthost: String

The TCP connection could not be established or resolved.

Handshakedetail: String

The handshake failed (certificate verification failures land here — OpenSSL reports them through the handshake result).

Iodetail: String

A read/write failed after the session was established.

Closed

The peer closed the TLS session (clean close_notify) during an operation that needed data.

Trait Implementations

Methods
to_string : (TlsError) fn(inout(self) : TlsError) -> String

Parameters

NameTypeNotes
selfTlsError

Returns: String

source : (TlsError) fn(inout(self) : TlsError) -> Option(dyn(ToString + ))

The error that caused this one, or .None at the root of the chain.

Rust's Error::source. Defaulted to .None, so an error with nothing underneath it implements the trait by saying only what it is; a wrapper overrides it to hand back what it wrapped. Walking the chain to the root cause works as of 2026-09-14 — the returned Dyn used to lose the Error trait on an erased receiver, so a caller could print one link but not follow it (#521, issues/fixed/self-trait-in-a-return-type-loses-the-trait-on-an-erased-receiver.md).

Parameters

NameTypeNotes
selfTlsError

Returns: Option(dyn(ToString + ))

TlsStream object
TlsStream

A connected TLS client stream.

Fields

NameTypeDescription
_tcpTcpStream
_ctx*(void)
_ssl*(void)
_rbio*(void)
_wbio*(void)
_closedbool

Trait Implementations

Dispose IoTraits
impl(TlsStream, ...)
connect : (TlsStream) fn(host : String, port : u16, io : Io) -> Impl(Future(TlsStream, IoExn))

Connect to host:port and complete a TLS handshake. Certificate verification and hostname checking are ON (the system trust store via SSL_CTX_set_default_verify_paths); SNI is sent.

Parameters

NameTypeNotes
hostString
portu16
ioIo

Returns: Impl(Future(TlsStream, IoExn))

read : (TlsStream) fn(self : TlsStream, buf : *(u8), size : usize, io : Io) -> Impl(Future(usize, IoExn))

Read decrypted bytes into buf. 0 = the peer closed the session.

Parameters

NameTypeNotes
selfTlsStream
buf*(u8)
sizeusize
ioIo

Returns: Impl(Future(usize, IoExn))

write : (TlsStream) fn(self : TlsStream, buf : *(u8), size : usize, io : Io) -> Impl(Future(usize, IoExn))

Write plaintext; returns the byte count accepted (all of size on success — SSL_write is all-or-nothing by default).

Parameters

NameTypeNotes
selfTlsStream
buf*(u8)
sizeusize
ioIo

Returns: Impl(Future(usize, IoExn))

write_str : (TlsStream) fn(self : TlsStream, data : str, io : Io) -> Impl(Future(usize, IoExn))

Write a str; returns the byte count written.

Parameters

NameTypeNotes
selfTlsStream
datastr
ioIo

Returns: Impl(Future(usize, IoExn))

write_string : (TlsStream) fn(self : TlsStream, data : String, io : Io) -> Impl(Future(usize, IoExn))

Write a String (matches TcpStream.write_string); byte count written.

Parameters

NameTypeNotes
selfTlsStream
dataString
ioIo

Returns: Impl(Future(usize, IoExn))

close : (TlsStream) fn(self : TlsStream, io : Io) -> Impl(Future(unit, IoExn))

Close: best-effort close_notify, then the socket. Frees the OpenSSL objects — the stream must not be used afterwards.

Parameters

NameTypeNotes
selfTlsStream
ioIo

Returns: Impl(Future(unit, IoExn))

impl(TlsStream, Dispose(...))
dispose : (TlsStream) fn(self : TlsStream) -> unit

A TlsStream dropped without close used to leak everything it owned — the socket descriptor AND the OpenSSL SSL/SSL_CTX pair, which TcpStream's own Dispose cannot reach. dispose releases both.

It does NOT send a close_notify: flushing the write BIO needs the event loop, and dispose runs in plain code (on a scope exit, an unwind, or the last RC release). close(io) is the graceful form and makes dispose a no-op afterwards; a peer that only sees the TCP FIN is what every language's TLS drop does.

Parameters

NameTypeNotes
selfTlsStream

Returns: unit

impl(TlsStream, IoTraits)
impl(TlsStream, IoTraits)
Methods
read_to_end : (TlsStream) fn(self : TlsStream, io : Io) -> Impl(Future(ArrayList(u8), IoExn))

Parameters

NameTypeNotes
selfTlsStream
ioIo

Returns: Impl(Future(ArrayList(u8), IoExn))

read_to_string : (TlsStream) fn(self : TlsStream, io : Io) -> Impl(Future(String, IoExn))

Parameters

NameTypeNotes
selfTlsStream
ioIo

Returns: Impl(Future(String, IoExn))

flush : (TlsStream) fn(self : TlsStream, io : Io) -> Impl(Future(unit, IoExn))

Parameters

NameTypeNotes
selfTlsStream
ioIo

Returns: Impl(Future(unit, IoExn))

write_all : (TlsStream) fn(self : TlsStream, buf : *(u8), size : usize, io : Io) -> Impl(Future(unit, IoExn))

Parameters

NameTypeNotes
selfTlsStream
buf*(u8)
sizeusize
ioIo

Returns: Impl(Future(unit, IoExn))

Functions

tls_available function
fn() -> bool

True when this build's TLS backend can connect (unix + OpenSSL).

Returns: bool