Module crypto/tls

crypto/tls
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. — stable modules only change additively; this one may still change.

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

impl(generic(T : Type), where(T <: ToString), T : (ToString))
impl(generic(T : Type), where(T <: ToString), T : (ToString), Format)
format : fn(self : Self, spec : str) -> String

Render self under spec. An unrecognised spec degrades to the plain to_string() rendering rather than failing.

Parameters

NameTypeNotes
selfSelf
specstr

Returns: String

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

Parameters

NameTypeNotes
selfTlsError

Returns: String

source : (TlsError) fn(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 is not expressible yet — the returned Dyn loses the Error trait on an erased receiver, so a caller can print one link but cannot follow it (#521, issues/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

IoTraits
impl(TlsStream, ...)
connect : (TlsStream) fn(host : String, port : u16, io : Io) -> Impl : (Future[Future](TlsStream) IoExn : 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[Future](TlsStream) IoExn : IoExn)

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

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

Parameters

NameTypeNotes
selfTlsStream
buf*(u8)
sizeusize
ioIo

Returns: Impl : (Future[Future](usize) IoExn : IoExn)

write : (TlsStream) fn(self : TlsStream, buf : *(u8), size : usize, io : Io) -> Impl : (Future[Future](usize) IoExn : 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[Future](usize) IoExn : IoExn)

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

Write a str; returns the byte count written.

Parameters

NameTypeNotes
selfTlsStream
datastr
ioIo

Returns: Impl : (Future[Future](usize) IoExn : IoExn)

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

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

Parameters

NameTypeNotes
selfTlsStream
dataString
ioIo

Returns: Impl : (Future[Future](usize) IoExn : IoExn)

close : (TlsStream) fn(self : TlsStream, io : Io) -> Impl : (Future[Future](unit) IoExn : 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[Future](unit) IoExn : IoExn)

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

Parameters

NameTypeNotes
selfTlsStream
ioIo

Returns: Impl : (Future[Future](ArrayList(u8)) IoExn : IoExn)

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

Parameters

NameTypeNotes
selfTlsStream
ioIo

Returns: Impl : (Future[Future](String) IoExn : IoExn)

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

Parameters

NameTypeNotes
selfTlsStream
ioIo

Returns: Impl : (Future[Future](unit) IoExn : IoExn)

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

Parameters

NameTypeNotes
selfTlsStream
buf*(u8)
sizeusize
ioIo

Returns: Impl : (Future[Future](unit) IoExn : IoExn)

Functions

tls_available function
fn() -> bool

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

Returns: bool