std/tls
import std/tls to use it. Source: src/std/tls.iyi at v0.18.0.
import std/tls::{TlsClient, TlsConfig, TlsError} conn = TlsClient.connect("example.com", 443).or_panic
conn.write("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n")
while (chunk = conn.read.or_panic) != ""
print chunk
end
conn.close import std/tls::{TlsServer, TlsServerConfig} config = TlsServerConfig.from_pem(File.read("chain.pem"), File.read("key.pem")).or_panic
config.alpn = ["h2", "http/1.1"]
conn = TlsServer.over(listener.accept.as(IyiSocket), config).or_panic
conn.read # what the client sentTLS 1.3 only, by default: a peer that speaks TLS 1.2 or earlier is refused, as it is by every implementation that has dropped the older versions' modes - a server always, a client unless its TlsConfig says otherwise (below). The three cipher suites RFC 8446 section 9.1 names (AES-128-GCM with SHA-256, AES-256-GCM with SHA-384, ChaCha20-Poly1305 with SHA-256), std/aead's; the key exchange is X25519, std/curve25519's
- and at a server secp256r1 too, std/p256's - both constant-time.
version says which version a connection speaks.
The client. A server that asks again (HelloRetryRequest) for a cookie is answered; one that asks for another group is refused, as X25519 is all this offers. The server's certificates are checked as std/x509 checks a chain - to the roots in TlsConfig#roots, or the system's (std/system_roots), for the host connected to - and then its CertificateVerify: ECDSA on P-256, P-384 or P-521 with the hash the scheme names, RSASSA-PSS (rsae) with SHA-256, -384 or -512, or Ed25519 - std/ecdsa's, std/rsa's and std/curve25519's. PKCS #1 v1.5 is taken in a certificate's own signature and never in CertificateVerify, as the RFC says. The server's Finished is checked before anything is sent under the application keys. There is no way to turn any of this off. A server that asks for a certificate is sent TlsConfig#identity's chain and a CertificateVerify when its request names the identity's scheme, and an empty Certificate otherwise, which section 4.4.2 allows. Not here: resumption and 0-RTT (NewSessionTicket is read and set aside).
TLS 1.2 at the client, and only when asked: config.min_version = TlsVersion::TLS12. It is here for the servers that never moved past 1.2 - appliances, old intranets, mail and payment endpoints - which a program has to reach; nothing else asks for it, and without it the ClientHello is 1.3's alone and a 1.2 ServerHello is refused as a server that does not speak 1.3. Asked for, it is held to what keeps 1.2 sound (RFC 5246, with RFC 7627's extended master secret and RFC 5746's renegotiation_info): one ClientHello offers both versions - supported_versions names 1.3 then 1.2, and after the three 1.3 suites come six of 1.2's - ECDHE signed by ECDSA (or Ed25519, RFC 8422) or by RSA, each with AES-128-GCM, AES-256-GCM (RFC 5289) or ChaCha20-Poly1305 (RFC 7905): ECDHE alone, for forward secrecy, and AEADs alone, with neither CBC's padding oracles nor RC4. A ServerHello with no supported_versions is 1.2's. It is refused when its random ends with RFC 8446 section 4.1.3's downgrade sentinel (a server that speaks 1.3 writes it when the ClientHello it read did not offer 1.3: something between took it out), when it echoes this ClientHello's session id (a resumption, which is not offered), when it chooses a suite not offered for 1.2, and when it does not do the extended master secret - which ties the master secret to the whole handshake (the triple handshake attack, 2014) and is required here, the refusal naming it. The key exchange is X25519 or secp256r1, as 1.3's; the ServerKeyExchange's signature is checked over both randoms and the parameters under the scheme it names, which must be one offered and the certificate key's - PKCS #1 v1.5 included, which 1.2 permits there, and ECDSA with the hash the scheme names on any of P-256, P-384 and P-521 (a 1.2 server's ECDSA certificate is on a curve the ClientHello names, RFC 8422 section 5.1.1, so one on P-384 refuses this ClientHello). The certificate is checked as for 1.3, and must be RSA for an ECDHE_RSA suite and ECDSA or Ed25519 for ECDHE_ECDSA. The PRF is P_SHA256, or P_SHA384 for the AES-256 suites. A CertificateRequest is answered with the identity's chain and a CertificateVerify over the handshake when the request takes the key's type and one of its schemes, with an empty Certificate otherwise. The ChangeCipherSpec is the key change: one, from the server, after this side's Finished, with no handshake bytes across it. AES-GCM's explicit nonce is the sequence number going out and whatever the record carries coming in; ChaCha20-Poly1305's nonce is 1.3's construction. There is no renegotiation: a HelloRequest after the handshake is answered with a no_renegotiation warning before this side's next write (a read writes nothing), and one during it is passed over, as section 7.4.1.1 says. No resumption either, so no session ticket is asked for. A 1.2 connection has no KeyUpdate: at key_limit records its writes end. Its exporter is RFC 5705's.
The server. TlsServerConfig.from_pem takes the chain, leaf first, and the leaf's private key: PKCS #8 (an Ed25519 key, RFC 8410 section 7, a P-256 one or an RSA one), SEC 1 or PKCS #1; the key must be the leaf's (a TlsIdentity, which a client presents the same way). CertificateVerify is signed by the key's kind: ed25519 or ecdsa_secp256r1_sha256 with std/curve25519 or std/p256, and for an RSA key rsa_pss_rsae_sha256, -384 or -512 - the first of those the client's signature_algorithms lists - with std/rsa, whose CRT runs through std/big's pow_mod_secret on a blinded message and is checked before it goes out. Each is constant-time: the key does not leak through how long a handshake takes. PKCS #1 v1.5 is never signed with, as RFC 8446 section 4.2.3 says, and an RSA key under 2048 bits is refused (std/rsa's floor). A full handshake with this server, measured here (--release; Python's ssl over loopback, the median of 200, its own side's work included): 0.64 ms with an Ed25519 key, 0.65 with P-256, 1.58 with RSA-2048 and 6.66 with RSA-4096. A client must take the key's schemes: RFC 8446 section 9.1 makes ecdsa_secp256r1_sha256 and rsa_pss_rsae_sha256 mandatory for every client; OpenSSL, Python, curl and Node take all three. The client's suite order chooses the suite; the server's ALPN order chooses the protocol, and a client that offers protocols and none of the server's is refused (no_application_protocol, RFC 7301). A share in X25519 or secp256r1 is taken, X25519's first - secp256r1's by std/p256's constant-time ECDH, its point checked to be on the curve. A ClientHello with a share in neither is asked again (HelloRetryRequest, no cookie) for X25519, or for secp256r1 where X25519 is not among its groups. Early data is refused: its records are passed over, up to sixteen of the largest, as section 4.2.10 says. No NewSessionTicket is sent. With client_roots the server asks for a client's certificate, checks its chain for clientAuth and its CertificateVerify, and refuses a client that sends none (certificate_required) unless client_required is false; peer_certificates is the chain. A handshake that fails sends the client the alert its TlsError names - in the clear before the server's keys, under them after.
A record is at most 2^14 bytes of content, as section 5.1 holds both sides to. KeyUpdate is answered. read answers "" once the peer's close_notify arrives; any other alert, or anything the protocol does not allow, is a TlsError saying what, and the connection is closed.
TlsKeySchedule and TlsRecordCipher are the key schedule (section 7.1) and the record protection (section 5.2) on their own, for what checks them against RFC 8448's traces.
Not audited. Its tests are RFC 8448's traces, a scripted server and a scripted client that each break the protocol one rule at a time, and OpenSSL, Python, curl and Node at the other end; that is evidence, not an audit.
TlsAlertstruct
The alerts of RFC 8446 section 6, by the code each is sent as.
pub struct TlsAlert
def initialize
endTlsClientclass
A connection to a server, its handshake done.
pub class TlsClient < TlsConnection
# A connection to *host* on *port*, its handshake done and the server's
# certificate checked for *host*.
def self.connect(host : String, port : Int32, config : TlsConfig = TlsConfig.new, timeout_ms : Int32 = -1) : TlsClient | TlsError
def handshake : TlsError | ::Nil
def initialize(socket : IyiSocket, host : String, config : TlsConfig)
# The handshake over a connected *socket*, for *host*, done within
# *timeout_ms* (none when 0).
def self.over(socket : IyiSocket, host : String, config : TlsConfig = TlsConfig.new, timeout_ms : Int32 = HANDSHAKE_TIMEOUT_MS) : TlsClient | TlsError
def server_side? : Bool
endTlsConfigclass
What a connection is checked against and offers.
pub class TlsConfig
# Protocols offered by ALPN (RFC 7301), most wanted first: `["h2",
# "http/1.1"]`. None offered when empty.
def alpn : Array(String)
# Protocols offered by ALPN (RFC 7301), most wanted first: `["h2",
# "http/1.1"]`. None offered when empty.
def alpn=(value : Array(String)) : Array(String)
# The certificate and key sent to a server that asks for one (mutual
# TLS), when the request names its scheme; none sent when nil.
def identity : TlsIdentity | ::Nil
# The certificate and key sent to a server that asks for one (mutual
# TLS), when the request names its scheme; none sent when nil.
def identity=(value : TlsIdentity | ::Nil) : TlsIdentity | ::Nil
def initialize
# The oldest version spoken. TLS 1.3 by default, and then a server that
# answers in 1.2 is refused as one that does not speak 1.3; `TLS12`
# offers 1.2 beside it (see the module's header for what it requires).
def min_version : TlsVersion
# The oldest version spoken. TLS 1.3 by default, and then a server that
# answers in 1.2 is refused as one that does not speak 1.3; `TLS12`
# offers 1.2 beside it (see the module's header for what it requires).
def min_version=(value : TlsVersion) : TlsVersion
# The time certificates are checked at; now when nil.
def now : Time | ::Nil
# The time certificates are checked at; now when nil.
def now=(value : Time | ::Nil) : Time | ::Nil
# The roots a server's chain must reach; the system's when nil, read
# once for the whole process and kept, since reading them is most of a
# handshake's own cost.
def roots : Array(Certificate) | ::Nil
# The roots a server's chain must reach; the system's when nil, read
# once for the whole process and kept, since reading them is most of a
# handshake's own cost.
def roots=(value : Array(Certificate) | ::Nil) : Array(Certificate) | ::Nil
def trusted_roots : Array(Certificate) | TlsError
endTlsConnectionclass
What either end of a connection does once it has keys: records read and written under them, alerts, KeyUpdate, close_notify. TlsClient and TlsServer are its two ends; each runs its own half of the handshake.
pub abstract class TlsConnection
def abort : Nil
# The protocol chosen by ALPN, if one was.
def alpn_protocol : String | ::Nil
def cipher_suite : String
# close_notify, then the socket closed.
def close : Nil
def closed? : Bool
# *length* bytes bound to this connection, for *label* and *context*
# (RFC 8446 section 7.5, RFC 5705's interface): the same at both ends and
# nowhere else - a channel binding, or keys for another protocol riding
# on this one. No context is the empty one, as TLS 1.3 has it; in TLS
# 1.2 (RFC 5705 section 4, over the extended master secret) an empty
# context is none, as OpenSSL's exporter takes it when told of none.
def export_keying_material(label : String, context : Array(UInt8) = [] of UInt8, length : Int32 = 32) : Array(UInt8)
# The alert the error names (its last parenthesised word), sent before
# the socket closes, by either end (RFC 8446 section 6.2): under the keys
# this side writes with, or in the clear before there are any. None for
# an alert the peer sent, whose sentence names no alert of this side's.
# A server then reads and drops what the client goes on sending, until
# it closes, for at most `LINGER_BYTES` and `LINGER_MS` in all: a close
# with the client's flight unread is a reset, which can take the alert
# from the client's buffer before it is read - a client refused for its
# certificate would read "reset", not why.
protected def fail(error : TlsError) : TlsError
def self.group_name(group : Int32) : String
def initialize(socket : IyiSocket, peer : String, place : String)
# Records one write key carries before the next (RFC 8446 section 5.5:
# AES-GCM's is 2^24.5 full records; ChaCha20-Poly1305's far more): past
# it, the next write moves to new keys first - or, in TLS 1.2, which has
# no KeyUpdate, is refused, and the connection with it. Settable, for a
# test.
def key_limit : UInt64
# Records one write key carries before the next (RFC 8446 section 5.5:
# AES-GCM's is 2^24.5 full records; ChaCha20-Poly1305's far more): past
# it, the next write moves to new keys first - or, in TLS 1.2, which has
# no KeyUpdate, is refused, and the connection with it. Settable, for a
# test.
def key_limit=(value : UInt64) : UInt64
# Moves this side's writes to new keys (section 4.6.3), asking the peer
# to move its own when *request*: what `write` does by itself at
# `key_limit`, for a program that wants it sooner. TLS 1.2 has no
# KeyUpdate: there it is a `TlsError`, and the connection stays open.
def key_update(request : Bool = false) : TlsError | ::Nil
# The chain the peer's certificate was checked through, leaf first: the
# server's, for a client; the client's, for a server that asked for one.
def peer_certificates : Array(Certificate)
# Up to *max* bytes the peer sent: "" once it has closed with
# close_notify. The socket's read timeout bounds the whole call.
def read(max : Int32 = 16384) : String | TlsError
# How long a read or a write of the connection under it may wait.
def read_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil
# Whether this is the server's end.
abstract def server_side? : Bool
# The version negotiated: "TLSv1.3", or "TLSv1.2" for a client whose
# server chose it.
def version : String
# *block* under a deadline *ms* from now (none when 0), cleared after.
protected def within(ms : Int32, &block : (-> T)) : T forall T
# Sends *data*, in records of at most 2^14 bytes; answers its size.
def write(data : String | Array(UInt8)) : Int32 | TlsError
def write_timeout_ms=(ms : Int32 | ::Nil) : Int32 | ::Nil
endTlsErrorstruct
What a connection answers when it cannot be made or kept, or a read that came back with nothing yet.
pub struct TlsError
# The alert this refusal sends the peer (`TlsAlert`), or nil: none for a
# failure of the socket, or for an alert the peer sent.
def alert : UInt8 | ::Nil
def cancelled? : Bool
# *message*, the alert's name after it when there is one: the sentence
# shows the alert, and the alert is the field, not the sentence.
def initialize(message : String, alert : UInt8 | ::Nil = nil, timed_out : Bool = false, cancelled : Bool = false)
# A read that waited past its timeout, or that was cancelled: nothing was
# lost and the connection is open - read again. Any other error ended it.
def timed_out? : Bool
def to_s : String
endTlsIdentityclass
A certificate chain, leaf first, and the key that signs for it - Ed25519, ECDSA on P-256 (std/p256) or RSA (std/rsa), each in constant time: what a server presents, and what a client presents when a server asks.
pub class TlsIdentity
# The ClientCertificateType a TLS 1.2 CertificateRequest must list for
# this key: rsa_sign (1), or ecdsa_sign (64) for P-256 and, by RFC 8422
# section 5.5, Ed25519.
def certificate_type : UInt8
# The chain as sent, leaf first, each certificate's DER.
def chain : Array(Array(UInt8))
# *chain*'s certificates, leaf first, and the 32-byte Ed25519 *seed*
# that is the leaf's private key.
def self.from_der(chain : Array(Array(UInt8)), seed : Array(UInt8)) : TlsIdentity | TlsError
# *chain*'s certificates, leaf first, and the P-256 *key* that is the
# leaf's.
def self.from_der(chain : Array(Array(UInt8)), key : P256PrivateKey) : TlsIdentity | TlsError
# *chain*'s certificates, leaf first, and the RSA *key* that is the
# leaf's (2048 bits or more, std/rsa's floor for signing).
def self.from_der(chain : Array(Array(UInt8)), key : RsaPrivateKey) : TlsIdentity | TlsError
# The chain in *certificates*' CERTIFICATE blocks, leaf first, and the
# leaf's private key in *private_key*: a PRIVATE KEY block (PKCS #8, an
# Ed25519 key as RFC 8410 section 7 writes it, a P-256 one as RFC 5915
# does, or an RSA one), an EC PRIVATE KEY block (SEC 1) or an RSA
# PRIVATE KEY block (PKCS #1) - or why they are not a certificate and
# key.
def self.from_pem(certificates : String, private_key : String) : TlsIdentity | TlsError
def initialize(chain : Array(Array(UInt8)), leaf : Certificate, schemes : Array(Int32), seed : Array(UInt8) | ::Nil, p256 : P256PrivateKey | ::Nil, rsa : RsaPrivateKey | ::Nil)
def leaf : Certificate
# The scheme to sign under for a peer that takes *offered* (its
# signature_algorithms, in its order): the first of them this key makes,
# or nil when it makes none.
def scheme_for(offered : Array(Int32)) : Int32 | ::Nil
# A scheme by the name RFC 8446 gives it.
def self.scheme_name(scheme : Int32) : String
# Schemes by name, for a sentence: "a, b or c".
def self.scheme_names(schemes : Array(Int32)) : String
# The schemes CertificateVerify can be signed under, by the key's kind:
# ed25519 (0x0807), ecdsa_secp256r1_sha256 (0x0403), or for an RSA key
# rsa_pss_rsae_sha256, -384 and -512 (0x0804 to 0x0806). PKCS #1 v1.5
# is never one: RFC 8446 section 4.2.3 keeps it out of CertificateVerify.
def schemes : Array(Int32)
# The leaf key's signature of *content* under *scheme*, one of
# `schemes`: what CertificateVerify carries.
def sign(scheme : Int32, content : Array(UInt8)) : Array(UInt8)
endTlsKeyScheduleclass
The key schedule of one connection (RFC 8446 section 7.1), over the cipher suite's hash: SHA-384 for TLS_AES_256_GCM_SHA384, SHA-256 for the other two.
pub class TlsKeySchedule
# Derive-Secret over the messages so far.
def derive_secret(secret : Array(UInt8), label : String, transcript : Array(UInt8)) : Array(UInt8)
# The early secret with no PSK, and the "derived" salt for the next stage.
def early_secret : Array(UInt8)
# HKDF-Expand-Label: the label prefixed "tls13 ", and the context, in an
# HkdfLabel.
def expand_label(secret : Array(UInt8), label : String, context : Array(UInt8), length : Int32) : Array(UInt8)
def extract(salt : Array(UInt8), ikm : Array(UInt8)) : Array(UInt8)
# A Finished message's verify_data under *secret* (section 4.4.4).
def finished(secret : Array(UInt8), transcript : Array(UInt8)) : Array(UInt8)
def handshake_secret(shared : Array(UInt8)) : Array(UInt8)
def hash(data : Array(UInt8)) : Array(UInt8)
def hash_size : Int32
def hmac(key : Array(UInt8), data : Array(UInt8)) : Array(UInt8)
def initialize(sha384 : Bool)
def master_secret(handshake_secret : Array(UInt8)) : Array(UInt8)
# The next generation of a traffic secret (section 7.2).
def next_secret(secret : Array(UInt8)) : Array(UInt8)
# TLS 1.2's PRF (RFC 5246 section 5): P_hash over this schedule's hash -
# SHA-256, or SHA-384 for a suite that names it - keyed by *secret*, over
# *label* and *seed*, *length* bytes of it.
def prf(secret : Array(UInt8), label : String, seed : Array(UInt8), length : Int32) : Array(UInt8)
def zeros : Array(UInt8)
endTlsRecordCipherclass
One direction's record protection (section 5.2): the AEAD keyed from a traffic secret, and the record sequence number its nonces are made of - or TLS 1.2's, keyed from its key block.
pub class TlsRecordCipher
# TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256.
def initialize(suite : Int32, schedule : TlsKeySchedule, secret : Array(UInt8))
# TLS 1.2's (RFC 5246 section 6.2.3.3) for one of its AEAD suites, *key*
# and *iv* from the key block: AES-GCM's four-byte salt, to which each
# record's eight-byte explicit nonce is added (RFC 5288 section 3), or
# ChaCha20-Poly1305's twelve bytes, XORed with the sequence number as TLS
# 1.3 does it (RFC 7905 section 2).
def initialize(suite : Int32, key : Array(UInt8), iv : Array(UInt8))
def key_iv : Array(UInt8)
# The type and content of a protected record whose 5-byte header is
# *header* and body *body*.
def open(header : Array(UInt8), body : Array(UInt8)) : ::Tuple(UInt8, Array(UInt8)) | TlsError
# The whole record that carries *content* of *type*, protected.
def seal(type : UInt8, content : Array(UInt8)) : Array(UInt8)
def sequence : UInt64
endTlsServerclass
A connection from a client, its handshake done: TLS 1.3 as a server, its certificate's key (Ed25519 or P-256) signing for it.
pub class TlsServer < TlsConnection
# The handshake, or why it failed - the client sent that alert first.
def handshake : TlsError | ::Nil
# The certificate and key this connection was shown under: the config's
# `identity`, or one of its `others` the client asked for by name.
def identity : TlsIdentity
def initialize(socket : IyiSocket, config : TlsServerConfig)
# The handshake with the client on *socket*. A handshake that fails
# sends the client the alert its error names, and the socket is closed.
def self.over(socket : IyiSocket, config : TlsServerConfig, timeout_ms : Int32 = HANDSHAKE_TIMEOUT_MS) : TlsServer | TlsError
# The host the client named (server_name, RFC 6066), if it named one.
def server_name : String | ::Nil
def server_side? : Bool
endTlsServerConfigclass
What a server presents and asks: its identity, the protocols it speaks by ALPN, and - for mutual TLS - the roots a client's certificate must chain to.
pub class TlsServerConfig
# *identity* shown to a client asking for a host its certificate names.
def add_identity(identity : TlsIdentity) : TlsServerConfig
# Protocols this server speaks by ALPN (RFC 7301), most wanted first; a
# client that offers some and none of these is refused. None when empty.
def alpn : Array(String)
# Protocols this server speaks by ALPN (RFC 7301), most wanted first; a
# client that offers some and none of these is refused. None when empty.
def alpn=(value : Array(String)) : Array(String)
def chain : Array(Array(UInt8))
def client_required : Bool
def client_required=(value : Bool) : Bool
# When set, the server asks for a client's certificate (a
# CertificateRequest) and checks it as std/x509 checks a client's
# (clientAuth) against these roots, then its CertificateVerify; a client
# that sends none is refused (certificate_required) while
# `client_required`. Not asked when nil.
def client_roots : Array(Certificate) | ::Nil
# When set, the server asks for a client's certificate (a
# CertificateRequest) and checks it as std/x509 checks a client's
# (clientAuth) against these roots, then its CertificateVerify; a client
# that sends none is refused (certificate_required) while
# `client_required`. Not asked when nil.
def client_roots=(value : Array(Certificate) | ::Nil) : Array(Certificate) | ::Nil
def self.from_der(chain : Array(Array(UInt8)), seed : Array(UInt8)) : TlsServerConfig | TlsError
def self.from_der(chain : Array(Array(UInt8)), key : P256PrivateKey) : TlsServerConfig | TlsError
def self.from_der(chain : Array(Array(UInt8)), key : RsaPrivateKey) : TlsServerConfig | TlsError
# `TlsIdentity.from_pem`'s identity, as a server's.
def self.from_pem(certificates : String, private_key : String) : TlsServerConfig | TlsError
def identity : TlsIdentity
# The identity a client asking for *name*, taking signatures under
# *schemes*, is shown: the first of `others` that names the host and
# signs under a scheme the client takes, else `identity` if it does; nil
# when none of them does.
def identity_for(name : String | ::Nil, schemes : Array(Int32)) : TlsIdentity | ::Nil
def initialize(identity : TlsIdentity)
def leaf : Certificate
# The time a client's certificate is checked at; now when nil.
def now : Time | ::Nil
# The time a client's certificate is checked at; now when nil.
def now=(value : Time | ::Nil) : Time | ::Nil
# More identities, each shown to a client that asks by server_name (RFC
# 6066) for a host its certificate names; `identity` to any other.
def others : Array(TlsIdentity)
def schemes : Array(Int32)
endTlsVersionenum
The versions a client may speak: TLS 1.3, or TLS 1.2 as well (RFC 5246), for a server that never moved past it.
pub enum TlsVersion : Int32
TLS12 = 0
TLS13 = 1
endError for TlsErrorimpl
impl Error for TlsError
def message : String
end
TLS 1.3 (RFC 8446), both ends, written in iyi from the RFCs: a client whose server's certificate std/x509 checked - which speaks TLS 1.2 too when asked to - and a server whose Ed25519, P-256 or RSA key signs for it, over std/socket.