Reference

std/tls/lib

std/tls/src/lib.trb

TLS over a TcpStream: TlsStream.connect for a client, TlsStream.accept with a ServerIdentity for a server, and a stream with the same two directions a TcpStream has (docs/design/NETWORK.md section 5).

const tcp = TcpStream.connectTo("example.test", 443).await()?
var stream = TlsStream.connect(tcp, "example.test").await()?
stream.send("GET / HTTP/1.1\r\nHost: example.test\r\n\r\n".bytes().toList()).await()?

The protocol is mbedTLS 3, TLS 1.2 and 1.3. A client checks the server's certificate for the name it asks for: on Windows the platform decides - its roots, its enterprise roots, its policies - and elsewhere the system's bundle of roots does; TlsSettings.trusted replaces both with roots of the program's own. There is no switch that turns the check off.

TLS is a state machine over bytes here: every wait is a wait of the TcpStream under it, so a TLS read is cancelled, timed out and paced exactly as a TCP read is.

type TlsSettings

type TlsSettings

What a client trusts. The default is the platform's judgement, which is what every other program of the machine trusts.

field trusted

trusted: List<String> = []

PEM certificates to trust instead of the platform: a private root of a company, the root of a test. Empty is the platform's roots, and a list that names any is only these.

type ServerIdentity

shared type ServerIdentity with Close

What a server proves who it is with: its certificate chain and the private key of the first certificate, both PEM. Parsed once and shared by every connection of a server; a shared type, because the key it holds is the runtime's.

field handle

protected var handle: Int

The runtime's handle: what a server session is started with. Read by TlsStream.accept, written by nothing.

fn of

static fn of(certificates: String, privateKey: String): Result<ServerIdentity, NetworkError>

The identity of a PEM chain - the server's own certificate first, then the intermediates - and the PEM private key that belongs to the first certificate.

Errors

  • NetworkError that isTlsFailure() where a certificate or the key cannot be read, or the key is not the certificate's.

fn close

var fn close()

Not documented.

type TlsStream

shared type TlsStream with Close

One TLS connection over a TcpStream: bytes in both directions, encrypted and authenticated. A shared type, like the stream it holds; it closes when the last reference to it goes, and the TCP stream with it.

fn connect

static fn connect(stream: TcpStream, serverName: String, settings: TlsSettings = TlsSettings()): Task<Result<TlsStream, NetworkError>>

The client's side: a handshake over stream with a server that must prove it is serverName.

Errors

  • NetworkError that isCertificateRejected() where the certificate is not trusted for that name, and isTlsFailure() where the handshake fails otherwise.

fn accept

static fn accept(stream: TcpStream, identity: ServerIdentity): Task<Result<TlsStream, NetworkError>>

The server's side: a handshake over an accepted stream, proving identity.

fn protocol

fn protocol(): String

The version the two sides agreed on: "TLSv1.3" or "TLSv1.2".

fn localAddress

fn localAddress(): Result<SocketAddress, NetworkError>

The address of this end.

fn remoteAddress

fn remoteAddress(): Result<SocketAddress, NetworkError>

The address of the other end.

fn receive

var fn receive(maximum: Int = 65536): Task<Result<Bytes?, NetworkError>>

At most maximum bytes, as soon as any were decrypted: Some(bytes), or None once the peer ended the session.

fn receiveBuffer

var fn receiveBuffer(maximum: Int = 65536): Task<Result<ArrayList<UInt8>?, NetworkError>>

TlsStream.receive as the ArrayList the bytes arrive in.

fn send

var fn send(bytes: Bytes): Task<Result<Void, NetworkError>>

Writes all of bytes, encrypted; finishes once the operating system took the ciphertext.

fn sendBuffer

var fn sendBuffer(bytes: ArrayList<UInt8>): Task<Result<Void, NetworkError>>

TlsStream.send for bytes that are an ArrayList already.

fn shutdown

var fn shutdown(): Task<Result<Void, NetworkError>>

Ends this half: a close_notify for the peer, then the end of the TCP stream's writing half.

fn source

fn source(chunk: Int = 65536): TlsSource

The reading direction as a Source: at most chunk bytes per next(), and None once the peer ended.

fn sink

fn sink(): TlsSink

The writing direction as a Sink: end() ends this half with close_notify.

fn close

var fn close()

Frees the session; the TCP stream closes with its own release.

type TlsSource

shared type TlsSource with Source<Bytes, NetworkError>

The reading direction of a TlsStream: a Source<Bytes, NetworkError>.

fn of

static fn of(stream: TlsStream, chunk: Int): TlsSource

The source of stream: what TlsStream.source answers.

fn next

var fn next(): Task<Result<Bytes?, NetworkError>>

Not documented.

fn close

var fn close()

Nothing of its own: the stream closes once its last holder is gone.

type TlsSink

shared type TlsSink with Sink<Bytes, NetworkError>

The writing direction of a TlsStream: a Sink<Bytes, NetworkError>.

fn of

static fn of(stream: TlsStream): TlsSink

The sink of stream: what TlsStream.sink answers.

fn add

var fn add(item: Bytes): Task<Result<Void, NetworkError>>

Not documented.

fn end

var fn end(): Task<Result<Void, NetworkError>>

Ends this half of the session with close_notify.

fn close

var fn close()

Nothing of its own: the stream closes once its last holder is gone.

type TlsResolver

type TlsResolver

DNS over TLS (RFC 7858): the lookups of std/network's Resolver, asked of one name server over a TLS connection to its port 853 - the question and the answer each after their length in two bytes, as over TCP (RFC 7766) - with the server authenticated by the name its certificate has to carry (RFC 8310's strict profile: a server that cannot prove serverName is never asked).

const resolver = TlsResolver(SocketAddress(IpAddress.tryFrom("192.0.2.53")?, 853), "dns.example.test")
const records = resolver.lookup(DomainName.tryFrom("example.test")?, RecordType.Aaaa).await()?

Every lookup opens a connection of its own and closes it with the answer: keeping one open for the next question is a later refinement. Times are milliseconds, because a field's default is a constant and a Duration is none.

field server

server: SocketAddress

The name server, usually on port 853.

field serverName

serverName: String

The name its certificate has to carry.

field settings

settings: TlsSettings = TlsSettings()

What the client trusts: the platform's roots by default.

field attemptMilliseconds

attemptMilliseconds: Int = 5000

How long connecting, the handshake and the answer may take together.

fn lookup

fn lookup(name: DomainName, recordType: RecordType): Task<Result<List<Record>, NetworkError>>

The records of recordType that name has, CNAME chains followed within the answer.

Errors

  • NetworkError that isHostNotFound() where the name does not exist (NXDOMAIN), and isNameServerFailure() where the server answered SERVFAIL, REFUSED or NOTIMP.
  • Every failure of TlsResolver.exchange.

fn exchange

fn exchange(name: DomainName, recordType: RecordType): Task<Result<Message, NetworkError>>

The whole response to a query for recordType at name, with a random identifier (RFC 5452).

Errors

  • NetworkError that isTimedOut() where the attempt's time passed, isCertificateRejected() where the server could not prove its name, and isNameServerFailure() where it answered something that is not the response.