std/tls
TLS 1.2 and 1.3 over a TcpStream - a client that checks the server's certificate the way the platform does, a server with an identity, a stream like the TCP one, and DNS over TLS.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Die generierte Referenz von std/tls zeigt jede öffentliche Deklaration mit Signatur, Doc-Kommentar und Beispielen.
std/tls puts TLS over a std/network TcpStream: TlsStream.connect for a client, TlsStream.accept
with a ServerIdentity for a server, and then a stream that receives and sends like the TCP one. The protocol is
mbedTLS 3.6, TLS 1.2 and 1.3, vendored into the runtime and linked only into a program that reaches std/tls; such a program carries
mbedTLS's Apache-2.0 license notice, unlike the rest of the runtime (MIT-0).
std/http is built on it: https URLs and Server.listen(..., tls: identity). It needs the network
capability inside a sandboxed script, and is not in the prelude.
Import#
use TlsStream, TlsSettings, ServerIdentity, ClientIdentity from "std/tls"use TcpStream, NetworkError from "std/network"
use TlsStream from "std/tls"
use textOf from "std/stream"
/** Asks a server for its front page over TLS and answers the first chunk of the answer. */
fn frontPage(host: String): Task<Result<String, NetworkError>> {
const tcp = TcpStream.connectTo(host, 443).await()?
var stream = TlsStream.connect(tcp, host).await()?
stream.send("GET / HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n".bytes()).await()?
const chunk = stream.receive().await()? ?? []
textOf(chunk).ok() ?? ""
}Declarations#
TlsStream#
shared type TlsStream with Close {
static fn connect(stream: TcpStream, serverName: String, settings: TlsSettings = TlsSettings()): Task<Result<Self, NetworkError>>
static fn accept(stream: TcpStream, identity: ServerIdentity, protocols: List<String> = [], clientRoots: List<String> = []): Task<Result<Self, NetworkError>>
fn protocol(): String
fn negotiatedProtocol(): String?
fn localAddress(): Result<SocketAddress, NetworkError>
fn remoteAddress(): Result<SocketAddress, NetworkError>
var fn receive(maximum: Int = 65536): Task<Result<Bytes?, NetworkError>>
var fn receiveBuffer(maximum: Int = 65536): Task<Result<Buffer<UInt8>?, NetworkError>>
var fn send(bytes: Bytes): Task<Result<Void, NetworkError>>
var fn sendBuffer(bytes: Buffer<UInt8>): Task<Result<Void, NetworkError>>
var fn shutdown(): Task<Result<Void, NetworkError>>
fn source(chunk: Int = 65536): TlsSource
fn sink(): TlsSink
var fn close()
}One TLS connection. connect runs the handshake as a client and checks that the server's certificate is trusted for
serverName; accept runs it as a server. Both finish before a byte of data crosses, so a certificate that is refused
is refused before the program sends anything. protocol() is TLSv1.3 or TLSv1.2. negotiatedProtocol() is the application protocol that ALPN chose, Some("h2")
or Some("postgresql"), and None where none was chosen. receive answers None once
the peer ended the session with close_notify; a peer that just drops the connection is a failure, because an attacker
can cut a connection but cannot forge its end. shutdown sends close_notify and ends the TCP half. Every wait is a
wait of the TcpStream under it, so a TLS read is cancelled and timed out (within) exactly as a TCP
read is. source() and sink() are the two directions as a Source<Bytes, NetworkError> and a
Sink<Bytes, NetworkError> (see std/stream).
TlsSettings#
type TlsSettings {
trusted: List<String> = []
protocols: List<String> = []
identity: ClientIdentity? = None
}What a client trusts. The default is the platform's judgement: on Windows the certificate chain goes to the system's
own verifier - its roots, the enterprise's roots, its policies - and elsewhere the system's bundle of roots decides.
trusted names PEM certificates to trust instead, and then only those: a company's private root, a test's own root.
There is no setting that turns the check off. protocols offers application protocols through ALPN (RFC 7301) in
preference order, ["h2", "http/1.1"]; TlsStream.accept takes the list a server accepts, and the first of its own
names that the client offers is chosen. A side with no list negotiates nothing and the data flows as before; two
lists with no name in common end the handshake with a failure that isTlsFailure(). A name that is empty, longer
than 255 bytes or holds a newline is refused by connect and accept, never cut short. identity is the certificate the client presents where the server asks for one (see ClientIdentity). Revocation is not checked, as in Go and rustls: a check goes to the
network and waits.
ServerIdentity#
shared type ServerIdentity with Close {
protected var handle: Int
static fn of(certificates: String, privateKey: String): Result<Self, NetworkError>
var fn close()
}What a server proves who it is with: its PEM certificate chain - its own certificate first, then the intermediates - and the PEM private key of the first certificate. It is read once and shared by every connection; a key that is not the certificate's is refused here, not at the first handshake.
ClientIdentity#
shared type ClientIdentity with Close {
protected var handle: Int
static fn of(certificates: String, privateKey: String): Result<Self, NetworkError>
var fn close()
}What a client proves who it is with (mutual TLS): its PEM certificate chain - its own certificate first, then the
intermediates - and the PEM private key of the first certificate, the same contract as ServerIdentity. A key that is
not the certificate's, or text that is no PEM, is refused by of with a failure that isTlsFailure(). Put it in
TlsSettings(identity: id); the certificate is sent only if the server asks for one, and a server that does not ignores
it. A server asks through clientRoots of TlsStream.accept: a non-empty list makes the client's certificate mandatory
and verifies it against exactly these PEM roots - never the platform's, a client certificate is issued by the server's
operator. A client that presents none or
another, or a list of roots of which one block cannot be read, is refused with isTlsFailure() (or
isCertificateRejected()); the unreadable roots fail before the handshake. A client sees a refusal on connect in TLS 1.2
and on its first receive or send in TLS 1.3, an error either way. There is no optional mode.
Transport#
shared type Transport {
static fn plain(stream: TcpStream): Self
static fn encrypted(stream: TcpStream, session: TlsStream): Self
fn isEncrypted(): Bool
fn tls(): TlsStream?
var fn upgrade(serverName: String, settings: TlsSettings = TlsSettings()): Task<Result<Void, NetworkError>>
var fn upgradeAccepting(identity: ServerIdentity, protocols: List<String> = [], clientRoots: List<String> = []): Task<Result<Void, NetworkError>>
var fn receiveBuffer(maximum: Int = 65536): Task<Result<Buffer<UInt8>?, NetworkError>>
var fn sendBuffer(bytes: Buffer<UInt8>): Task<Result<Void, NetworkError>>
var fn finish(): Task<Result<Void, NetworkError>>
fn remote(): SocketAddress?
}One connection that is plain TCP or TLS over TCP, behind the same four operations (receiveBuffer, sendBuffer,
finish, remote), for a protocol that does not care which. Transport.plain(stream) starts in the clear and
Transport.encrypted(stream, session) wraps a stream whose handshake TlsStream.connect or TlsStream.accept ran;
isEncrypted() says which it is now, and tls() is the session (for negotiatedProtocol() and protocol()), None
while the bytes are plain.
The upgrade is what protocols that ask for TLS after they began need - Postgres's TLS request, MySQL's SSL request,
SMTP's STARTTLS: upgrade(serverName, settings) runs the client's handshake over the plain stream, with the
semantics of TlsStream.connect, and upgradeAccepting(identity, protocols, clientRoots) the server's, with those of
TlsStream.accept. Every byte after it is encrypted. An upgrade of a transport that is encrypted already is refused
with a NetworkError that is not a TLS failure, and the transport goes on as it was. A handshake that fails
(isCertificateRejected(), isTlsFailure()) leaves the stream holding half a handshake, so the transport is unusable
after it: every later operation fails with isClosed().
A transport buffers nothing. A caller that reads through a buffer of its own has to have consumed it before the
upgrade: bytes the peer sent in the clear after the request for TLS are an injection (CVE-2011-0411), and a caller that
finds some left closes the connection instead of upgrading it. No send, receive or upgrade may be pending while an
upgrade runs. std/http reads and writes http and https through this type.
var transport = Transport.plain(TcpStream.connectTo("mail.example.test", 587).await()?)
// the greeting, `EHLO`, then the request for TLS and the server's `220`, all in the clear
transport.sendBuffer("STARTTLS\r\n".bytes()).await()?
const _ = transport.receiveBuffer().await()?
transport.upgrade("mail.example.test").await()?
print transport.isEncrypted()TlsResolver#
type TlsResolver with NameResolver {
server: SocketAddress
serverName: String
settings: TlsSettings = TlsSettings()
attemptMilliseconds: Int = 5000
fn lookup(name: DomainName, recordType: RecordType): Task<Result<List<Record>, NetworkError>>
fn exchange(name: DomainName, recordType: RecordType): Task<Result<Message, NetworkError>>
}DNS over TLS (RFC 7858): the lookups of std/network's Resolver, asked of one name server over TLS -
on port 853, the port RFC 7858 assigns - which has to prove serverName before a question is sent. The question and the answer go as over
TCP, each after its length in two bytes, one connection per lookup; the messages are std/dns's. lookup
answers the records of the type, CNAME chains followed, and fails with isHostNotFound() for a name that does not
exist and isNameServerFailure() for a server that answered SERVFAIL, REFUSED or NOTIMP.
Failures#
A failure of TLS is a NetworkError: isCertificateRejected() where the peer's certificate is not
trusted for the name, isTlsFailure() for every other failure of the protocol, and show() says why in the words of
the platform's verifier or of mbedTLS.
Related#
- std/network - the TCP stream under TLS, and
NetworkError. - std/http - HTTPS in the client and the server, over a
Transport. - std/dns - the messages
TlsResolversends and reads. - docs/design/NETWORK.md - section 5, why mbedTLS and the platform's verifier.
- The standard library - the other packages.