TLS

TLS wraps TCP connections and listeners with OpenSSL-backed TLS state while keeping deadlines and socket lifecycle aligned with the underlying transport. Concrete-address dialing and listening use the same connect and listen entrypoints as the string-address surface. String-address client dialing uses the resolver/policy layer as Reseau.TCP.connect; see Name Resolution for that part of the stack.

Configuration, Types, and Errors

The main TLS building blocks are:

Reseau.TLS.ConfigType
Config(; ...)

Reusable TLS configuration for client and server sessions.

Keyword arguments:

  • server_name: hostname or IP literal used for certificate verification. For clients, this also drives SNI when it is a hostname. If omitted, connect will try to derive it from the dial target.
  • verify_peer: whether to validate the remote certificate chain and peer name.
  • client_auth: server-side client certificate policy.
  • cert_file / key_file: PEM-encoded certificate chain and private key. Servers must provide both; clients may provide both for mutual TLS.
  • ca_file: CA bundle or hashed CA directory used to verify remote servers. When omitted, client verification uses NetworkOptions.ca_roots_path().
  • client_ca_file: CA bundle or hashed CA directory used by servers to verify client certificates. This is required for server configs that verify presented client certs.
  • alpn_protocols: ordered ALPN protocol preference list.
  • handshake_timeout_ns: optional cap, in monotonic nanoseconds, applied only while the handshake is running. Existing transport deadlines still win if they are earlier.
  • min_version / max_version: TLS protocol version bounds. nothing leaves the bound unset.

Returns a reusable immutable Config.

Throws ConfigError if the keyword combination is internally inconsistent.

source
Reseau.TLS.ConnectionStateType
ConnectionState

Snapshot of negotiated TLS connection state.

Fields:

  • handshake_complete: whether the TLS handshake has finished successfully.
  • version: negotiated TLS protocol version string as reported by OpenSSL.
  • alpn_protocol: negotiated ALPN protocol, or nothing if ALPN was not used.
source
Reseau.TLS.ConnType
Conn

TLS stream wrapper over one TCP.Conn.

Conn is safe for one concurrent reader and one concurrent writer. Handshake, read, and write all have separate locks so lazy handshakes and shutdown can coordinate without corrupting the OpenSSL state machine. Because Conn <: IO, standard Base stream helpers like read, read!, readbytes!, eof, and write apply directly to decrypted application data.

source
Reseau.TLS.ListenerType
Listener

TLS listener wrapper over TCP.Listener.

Accepted connections are wrapped in server-side TLS state but are not handshaken until handshake! or the first read/write call.

source
Reseau.TLS.ConfigErrorType
ConfigError

Raised when TLS configuration is invalid.

This is thrown before any network I/O starts, typically while normalizing a Config or constructing an SSL_CTX.

source
Reseau.TLS.TLSErrorType
TLSError

Raised when TLS handshake/read/write/close operations fail.

code is usually an OpenSSL SSL_get_error result, though wrapper paths may set it to 0 when the failure is higher-level than one OpenSSL call. cause preserves the underlying Julia-side exception when the failure originated in the transport/poller layer instead of OpenSSL itself.

source
Reseau.TLS.TLSHandshakeTimeoutErrorType
TLSHandshakeTimeoutError

Raised when handshake exceeds the configured timeout.

This is kept separate from TLSError because handshake timeouts are often configured and handled distinctly from generic TLS failures.

source
Reseau.TLS.DeadlineExceededErrorType
DeadlineExceededError

Alias for the transport deadline timeout type used by TLS.

Catch TLS.DeadlineExceededError for direct listener accept timeouts and when inspecting TLSError.cause. Higher-level TLS operations may wrap deadline expiry in TLSError or TLSHandshakeTimeoutError so callers can keep operation-specific handling.

source

ClientAuthMode is an enum-like policy surface with the following cases:

  • TLS.ClientAuthMode.NoClientCert
  • TLS.ClientAuthMode.RequestClientCert
  • TLS.ClientAuthMode.RequireAnyClientCert
  • TLS.ClientAuthMode.VerifyClientCertIfGiven
  • TLS.ClientAuthMode.RequireAndVerifyClientCert

For server-side verified client-certificate auth, provide client_ca_file explicitly in Config.

Client and Server Construction

You can either dial and handshake a client in one step from a concrete TCP.SocketAddr or string address, or wrap an existing TCP.Conn manually:

Reseau.TLS.clientFunction
client(tcp, config) -> Conn

Wrap an established TCP.Conn in client-side TLS state.

The handshake is deferred until handshake! or the first read/write operation.

Throws ConfigError or TLSError if the TLS state cannot be initialized.

source
Reseau.TLS.serverFunction
server(tcp, config) -> Conn

Wrap an established TCP.Conn in server-side TLS state.

The handshake is deferred until handshake! or the first read/write operation.

Throws ConfigError or TLSError if the TLS state cannot be initialized.

source
Reseau.TLS.connectFunction
connect(remote_addr; kwargs...) -> Conn
connect(remote_addr, local_addr; kwargs...) -> Conn

Connect to a concrete TCP.SocketAddr, negotiate TLS, and return a fully handshaken client connection.

This is the direct-address counterpart to the string-address connect(network, address; ...) overloads. The underlying socket setup is delegated to TCP.connect, after which the returned transport is wrapped in TLS and handshaken immediately.

TLS keyword arguments are forwarded to Config. If server_name is omitted, it is inferred from the concrete remote address when possible so peer verification and SNI can follow the same rules as the string-address client path.

source
connect(network, address; kwargs...) -> Conn

Connect to address, negotiate TLS, and return a fully handshaken client connection.

Network-resolution keyword arguments are:

  • timeout_ns
  • deadline_ns
  • local_addr
  • fallback_delay_ns
  • resolver
  • policy

TLS keyword arguments are forwarded to Config:

  • server_name
  • verify_peer
  • client_auth
  • cert_file
  • key_file
  • ca_file
  • client_ca_file
  • alpn_protocols
  • handshake_timeout_ns
  • min_version
  • max_version

If server_name is omitted, it is derived from address when possible so SNI and peer verification use the dial target's host name automatically.

source
connect(address; kwargs...) -> Conn

Convenience shorthand for connect("tcp", address; kwargs...).

source
Reseau.TLS.listenFunction
listen(network, address, config; backlog=128, reuseaddr=true) -> Listener

Create a TLS listener by first creating a TCP listener and then associating a server Config with accepted connections.

Accepted Conns are returned in lazy-handshake form.

Throws ConfigError if the server config is invalid and propagates any listener creation errors from TCP.listen.

source
listen(local_addr, config; backlog=128, reuseaddr=true) -> Listener

Create a TLS listener from a concrete local TCP.SocketAddr.

This is the direct-address counterpart to listen(network, address, config; ...). The underlying TCP listener is created with TCP.listen, then accepted connections are wrapped in lazy-handshake TLS state.

source
Reseau.TLS.acceptFunction
accept(listener) -> Conn

Accept one inbound TCP connection and wrap it in server-side TLS state.

The returned Conn has not handshaken yet. If the listener deadline expires, accept(listener) throws DeadlineExceededError.

source
Reseau.TLS.handshake!Function
handshake!(conn)

Run the TLS handshake to completion if it has not already finished.

This method is idempotent. Concurrent callers serialize through handshake_lock, so only one task drives OpenSSL while the others observe the completed state afterward.

Returns nothing.

Throws:

  • TLSHandshakeTimeoutError when config.handshake_timeout_ns expires
  • TLSError for OpenSSL or transport failures
  • EOFError if the peer closes cleanly during the handshake
source

Important behaviors to keep in mind:

  • connect returns a fully handshaken client connection for both concrete-address and string-address dialing.
  • listen returns a TLS listener whose accepted connections are lazy-handshake wrappers.
  • client and server are the direct wrapping APIs when you already control the underlying TCP.Conn.
  • handshake! is idempotent, so eager and lazy handshake styles can coexist safely.

I/O, Lifecycle, Deadlines, and Inspection

TLS connections still behave like Julia streams, but now read and write operate on plaintext application data while OpenSSL handles record framing underneath:

Base.read!Method
read!(conn, buf) -> buf

Read exactly length(buf) decrypted bytes into buf or throw EOFError.

Because Conn <: IO, Base's generic read! implementation already supports mutable byte views like @view bytes[2:5] in addition to plain vectors.

Use readbytes! or readavailable when you want a count-returning read that may stop early.

source
Base.readbytes!Method
readbytes!(conn, buf, nb=length(buf); all::Bool=true) -> Int

Read up to nb decrypted bytes into buf, returning the byte count.

If all is true (the default), the call keeps reading until nb bytes have been transferred, EOF is reached, or an error occurs. If all is false, at most one underlying TLS read is performed.

Resizable Vector{UInt8} buffers grow when needed, matching Julia's standard readbytes! behavior. Fixed-size contiguous byte views must satisfy nb <= length(buf).

source
Base.readavailableMethod
readavailable(conn) -> Vector{UInt8}

Read and return decrypted plaintext that is ready without requiring a full-buffer exact read.

source
Base.eofMethod
eof(conn) -> Bool

Report whether the peer has cleanly finished the TLS stream.

source
Base.isopenMethod
isopen(conn) -> Bool

Return true while both the TLS state and underlying TCP transport remain open.

source
Base.isopenMethod
isopen(listener) -> Bool

Return true while the wrapped TCP listener remains open.

source
Base.writeMethod
write(conn, buf) -> Int
write(conn, buf, nbytes) -> Int

Write plaintext application bytes through the TLS connection.

write(conn, buf) attempts to write the entire buffer. The fixed-size byte-buffer overload allows callers to cap the write at nbytes.

source
Base.closeMethod
close(conn)

Close the TLS connection and the underlying TCP transport.

If the handshake completed, this best-effort sends a TLS close_notify alert before closing the socket. The method is idempotent and returns nothing.

source
Base.closeMethod
close(listener)

Close the underlying TCP listener. Repeated closes are treated as no-ops.

source
Base.closewriteMethod
closewrite(conn)

Send TLS shutdown on the write side and mark future writes as permanently failed.

This is the TLS analogue of half-closing a TCP socket. It requires the handshake to have completed because the TLS close-notify alert is itself a TLS record.

Returns nothing.

Throws TLSError if the TLS shutdown path fails.

source
Reseau.TLS.set_deadline!Function
set_deadline!(conn, deadline_ns)

Set both read and write deadlines on the underlying transport.

deadline_ns is interpreted using the same monotonic-clock convention as TCP and IOPoll: 0 clears the deadline, negative values mean the deadline has already expired, and positive values are absolute time_ns() values.

source
set_deadline!(listener, deadline_ns)

Set the accept deadline on the underlying TCP listener.

This affects accept(listener) only. The returned Conn still uses its own connection and handshake deadlines afterward. Expired accept waits throw DeadlineExceededError.

source
Reseau.TLS.set_read_deadline!Function
set_read_deadline!(conn, deadline_ns)

Set the read deadline on the underlying transport. See set_deadline! for the timestamp convention.

source
Reseau.TLS.set_write_deadline!Function
set_write_deadline!(conn, deadline_ns)

Set the write deadline on the underlying transport. See set_deadline! for the timestamp convention.

source
Reseau.TLS.local_addrFunction
local_addr(conn)

Return the local TCP.SocketAddr for the wrapped transport.

source
local_addr(listener)

Return the local listening address for the wrapped TCP listener.

This is an alias for addr(listener).

source
Reseau.TLS.net_connFunction
net_conn(conn) -> TCP.Conn

Expose the underlying TCP connection.

Callers that need transport-level inspection can reach through the TLS wrapper without reconstructing the socket state.

source
Reseau.TLS.connection_stateFunction
connection_state(conn) -> ConnectionState

Return a snapshot of the currently negotiated TLS state.

This does not force a handshake; if the handshake has not yet run, the returned ConnectionState reports handshake_complete=false.

source
Reseau.TLS.addrFunction
addr(listener)

Return the local listening address for the wrapped TCP listener.

source

Two details matter in practice:

  • read!(conn, buf) fills buf or throws EOFError; use readbytes! or readavailable when you want short-read behavior.
  • TLS deadlines are delegated to the wrapped TCP transport, so the timeout model matches TCP exactly.
  • A timed-out TLS write is treated as a permanent write failure because partial record emission leaves future writes unsafe.
  • close(conn) and close(listener) are idempotent, and isopen is available on both TLS connections and listeners.

TLS.DeadlineExceededError is the public alias for the underlying transport timeout type. accept(listener) throws it directly when the listener deadline expires. Higher-level TLS operations may instead raise TLSError with the timeout in cause, or a handshake-specific TLSHandshakeTimeoutError.

Practical Usage Notes

  • If server_name is omitted, connect derives it from the dial target when possible so SNI and certificate verification use that host name automatically.
  • If ca_file is omitted for outbound verification, Reseau falls back to NetworkOptions.ca_roots_path() when that path is available.
  • connection_state does not force the handshake to run; it reports the current negotiated state as-is.
  • set_deadline! also applies to TLS.Listener, where it sets the accept deadline only. local_addr(listener) is available as an alias for addr.

Where To Go Next

  • Read TCP for the underlying transport model and socket lifecycle.
  • Read Name Resolution for the advanced resolver and address-family policy controls shared by TCP.connect and TLS.connect.
  • Read API Reference for the canonical TLS docstrings.