Browse the docs

silk.tls.client.

A TLS 1.3 client state machine that authenticates an HTTPS server over a byte transport that you drive.

When to use#

Most programs use silk.tls.connection, which drives this client over a ByteDuplex. Use Client directly when you need full control of the transport, for example in an event loop. Make it with Client.make, then move bytes between it and the transport until it reports Authenticated, and then read and write application data.

Details#

The client supports TLS 1.3 only, with the cipher suites ChaCha20-Poly1305, AES-128-GCM, and AES-256-GCM, and the key exchange groups X25519 and P-256. It checks the certificate chain against your TrustSnapshot, the host name of your ReferenceIdentity, the CertificateVerify signature, and the Finished message. It gives no application data before all checks pass.

Each call returns a Progress: how many bytes it took or wrote, and the next Demand. Output works in two steps: write Client.pendingOutput to the transport, and then call Client.ackWritten with the number of bytes written. The client does not keep your buffers. ClientLimits bound all memory and work.

Errors#

  • TlsError for an alert from the server, a protocol error, a certificate that fails, a limit, or a call in the wrong state. An error is permanent for the client.

  • OutOfMemoryError from Client.make if the Allocator fails.

Gotchas#

You provide the trust, the time, the random source, and the transport. The client supports one TLS 1.3 profile, not all of the Web PKI. Dropping the client does not send close_notify. Call Client.closeWrite first. Secrets are not erased.

Examples#

Make a client and send its ClientHello#

silk
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
import silk.tls.https_identity { HttpsIdentity, IdentityError, OriginHost, ReferenceIdentity }
import silk.collections.list { List }
import silk.system.random { Random }
import silk.core.result { Result }
import silk.system.system_clock { SystemClock }
import silk.tls.client { AlpnConfig, Client, ClientConfig, ClientLimits, Progress, TlsError }
import silk.tls.trust_anchor { TrustAnchor }
import silk.tls.trust_snapshot { SnapshotLimits, TrustSnapshot, TrustSourceError }
import silk.num.usize

// Not secure. Real programs use a `Random` provider from the operating system.
struct ExampleRandom {
  next: usize,
}

impl Random for ExampleRandom {
  effect fn fillBytes(self: &mut Self, output: &mut [u8]) -> () {
    let mut index: usize = 0
    while index < output.length {
      output[index] = usize.toU8((self.next + index) % 251 + 1)
      index = index + 1
    }
    self.next = self.next + output.length
    return ()
  }
}

pub effect fn main() -> i32
  ! OutOfMemoryError {
  let mut allocator = Allocator.systemAllocatorProvider()
  let mut random = ExampleRandom { next: 1 }
  let reference = match HttpsIdentity.reference(OriginHost<'static>.Dns { bytes: b"example.com" }) {
    Result<ReferenceIdentity, IdentityError>.Success { value } => value
    Result<ReferenceIdentity, IdentityError>.Failure { error: _ } => {
      return 1
    }
  }

  // An empty snapshot trusts no server. Real programs load CA certificates.
  let trust = match TrustSnapshot.fromAnchors(List<TrustAnchor>.make(), SnapshotLimits.defaults()) {
    Result<TrustSnapshot, TrustSourceError>.Success { value } => move value
    Result<TrustSnapshot, TrustSourceError>.Failure { error: _ } => {
      return 1
    }
  }
  let config = ClientConfig {
    reference: reference,
    alpn: AlpnConfig.defaults(),
    limits: ClientLimits.defaults()
  }
  let made = run Client.make(&config, move trust, SystemClock.make(1789156800, 0))
    |> Effect.provideMut<Random>(&mut random)
    |> Effect.provideMut<Allocator>(&mut allocator)
  let mut client = match move made {
    Result<Client, TlsError>.Success { value } => move value
    Result<Client, TlsError>.Failure { error: _ } => {
      return 1
    }
  }

  // The ClientHello is ready. Write it to the transport, then acknowledge the bytes written.
  let length = Client.pendingOutput(&client).length
  if length == 0 {
    return 1
  }
  return match Client.ackWritten(&mut client, length) {
    Result<Progress, TlsError>.Success { value: _ } => 0
    Result<Progress, TlsError>.Failure { error: _ } => 1
  }
}

Import as Client with import silk.tls.client { Client }.

Public declarations: 15.

NamedGroup#

silk
pub enum NamedGroup

One key-exchange group selected for the authenticated connection.

X25519#

silk
X25519

X25519 with a 32-byte encoded public key.

P256#

silk
P256

The NIST P-256 curve with a 65-byte uncompressed encoded point.

AlpnProtocol#

silk
pub struct AlpnProtocol<'a>

One borrowed opaque ALPN protocol offered in caller order.

Field bytes#

silk
pub bytes: &'a [u8]

The nonempty protocol bytes, without a length prefix.

AlpnConfig#

silk
pub union AlpnConfig<'a>

Borrowed ALPN policy copied by Client.make.

Disabled#

silk
Disabled

Sends no ALPN extension and accepts no server selection.

Offered#

silk
Offered

Sends one ordered, nonempty, duplicate-free protocol list.

Field protocols#

silk
pub protocols: &'a [AlpnProtocol<'a>]

The protocols in preference order.

Field required#

silk
pub required: bool

Whether absence of a server selection is a failure.

Implementation Copy for AlpnProtocol<'a>#

silk
impl<'a> Copy for AlpnProtocol<'a>

Implementation Copy for AlpnConfig<'a>#

silk
impl<'a> Copy for AlpnConfig<'a>

Implementation AlpnConfig<'a>#

silk
impl<'a> AlpnConfig<'a>

Associated function defaults#

silk
pub fn defaults() -> AlpnConfig<'a>

Returns the default policy, which sends no ALPN extension.

ClientLimits#

silk
pub struct ClientLimits

Inclusive local resource policy for one client connection. The limits bound all memory and work of the client.

Field handshakeBodyBytes#

silk
pub handshakeBodyBytes: usize

Maximum body bytes in one handshake message.

Field handshakeBytes#

silk
pub handshakeBytes: usize

Maximum total initial-handshake bytes, including four-byte headers.

Field handshakeMessages#

silk
pub handshakeMessages: usize

Maximum initial-handshake message count.

Field peerCertificates#

silk
pub peerCertificates: usize

Maximum peer certificate count, including the leaf.

Field certificateBytes#

silk
pub certificateBytes: usize

Maximum DER bytes in one peer certificate.

Field certificateTotalBytes#

silk
pub certificateTotalBytes: usize

Maximum aggregate peer DER bytes.

Field cookieBytes#

silk
pub cookieBytes: usize

Maximum echoed HelloRetryRequest cookie bytes.

Field extensionBytes#

silk
pub extensionBytes: usize

Maximum bytes in one extension vector.

Field emptyRecords#

silk
pub emptyRecords: usize

Maximum consecutive empty application or compatibility records.

Field postHandshakeControls#

silk
pub postHandshakeControls: usize

Maximum consecutive post-handshake control messages without positive application content.

Field tickets#

silk
pub tickets: usize

Maximum accepted NewSessionTicket messages.

Field ticketBytes#

silk
pub ticketBytes: usize

Maximum total encoded NewSessionTicket bytes.

Field certificateDecode#

silk
pub certificateDecode: DecodeLimits

Strict certificate decoder policy.

Field sanDecode#

silk
pub sanDecode: SanDecodeLimits

Strict SAN adapter policy.

Field identity#

silk
pub identity: IdentityLimits

HTTPS identity policy.

Field path#

silk
pub path: ValidationLimits

Certificate-path policy.

Implementation Copy for ClientLimits#

silk
impl Copy for ClientLimits

Implementation ClientLimits#

silk
impl ClientLimits

Associated function defaults#

silk
pub fn defaults() -> ClientLimits

Returns the finite TLS client defaults.

ClientConfig#

silk
pub struct ClientConfig<'a>

Borrowed client configuration that construction copies into owned storage.

Field reference#

silk
pub reference: ReferenceIdentity<'a>

A reference already admitted by HttpsIdentity.reference. Construction revalidates it.

Field alpn#

silk
pub alpn: AlpnConfig<'a>

ALPN policy, with opaque protocols in caller preference order.

Field limits#

silk
pub limits: ClientLimits

Finite connection limits and nested certificate policies.

Demand#

silk
pub enum Demand

The next action or observable connection event of a Client, in Progress.demand.

NeedInput#

silk
NeedInput

Supply peer transport bytes.

NeedOutput#

silk
NeedOutput

Write and acknowledge the complete pending output suffix before other work.

PlaintextReady#

silk
PlaintextReady

Read authenticated buffered application plaintext.

Authenticated#

silk
Authenticated

Observe the one-shot authentication transition and its metadata.

PeerClosed#

silk
PeerClosed

The peer receive direction closed cleanly.

Closed#

silk
Closed

Both directions closed cleanly and no output remains.

Progress#

silk
pub struct Progress

Exact progress from one Client operation: how many bytes it took or wrote, and the next Demand.

Field consumed#

silk
pub consumed: usize

The accepted prefix of caller input or application plaintext.

Field written#

silk
pub written: usize

The bytes copied into a caller plaintext destination.

Field demand#

silk
pub demand: Demand

The next action or event under the fixed precedence rule.

ClientOperation#

silk
pub enum ClientOperation

A public operation rejected because of caller state.

FeedInput#

silk
FeedInput

Transport input feed.

Progress#

silk
Progress

State-machine progress without new bytes.

AckWritten#

silk
AckWritten

Pending-output acknowledgment.

ReadPlaintext#

silk
ReadPlaintext

Application plaintext read.

WritePlaintext#

silk
WritePlaintext

Application plaintext write.

RequestKeyUpdate#

silk
RequestKeyUpdate

Local traffic-key update request.

CloseWrite#

silk
CloseWrite

Local write-direction close.

EndInput#

silk
EndInput

Transport input end.

Implementation Debug for ClientOperation#

silk
impl Debug for ClientOperation

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as FeedInput.

ProtocolReason#

silk
pub enum ProtocolReason

A protocol grammar or state-machine rule rejected from the peer.

Record#

silk
Record

A record has an invalid content type, version, length, padding, or inner framing.

UnexpectedMessage#

silk
UnexpectedMessage

A handshake message arrived in an invalid order.

InvalidLength#

silk
InvalidLength

A handshake message or vector is truncated or has trailing bytes.

Extension#

silk
Extension

An extension is duplicated, unsolicited, misplaced, or malformed.

ServerHello#

silk
ServerHello

ServerHello does not select the admitted TLS 1.3 profile.

HelloRetryRequest#

silk
HelloRetryRequest

HelloRetryRequest violates its single-retry and changed-ClientHello rules.

CompatibilityCcs#

silk
CompatibilityCcs

Compatibility CCS is outside its window or not exactly {1}.

CertificateRequest#

silk
CertificateRequest

A CertificateRequest has invalid framing, duplication, or a profile-forbidden extension.

Certificate#

silk
Certificate

A peer Certificate message violates its TLS envelope.

PostHandshake#

silk
PostHandshake

A NewSessionTicket or KeyUpdate message violates its TLS envelope.

InternalState#

silk
InternalState

The client reached an impossible private transition.

Implementation Debug for ProtocolReason#

silk
impl Debug for ProtocolReason

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as Record.

TlsLimitKind#

silk
pub enum TlsLimitKind

A named finite resource exhausted by peer input or configuration.

HandshakeBodyBytes#

silk
HandshakeBodyBytes

One handshake body.

HandshakeBytes#

silk
HandshakeBytes

Aggregate initial-handshake headers and bodies.

HandshakeMessages#

silk
HandshakeMessages

Initial-handshake message count.

PeerCertificates#

silk
PeerCertificates

Peer certificate count.

CertificateBytes#

silk
CertificateBytes

DER bytes in one peer certificate.

CertificateTotalBytes#

silk
CertificateTotalBytes

Aggregate peer certificate DER bytes.

CookieBytes#

silk
CookieBytes

HelloRetryRequest cookie bytes.

ExtensionBytes#

silk
ExtensionBytes

One extension vector.

EmptyRecords#

silk
EmptyRecords

Consecutive empty application or compatibility records.

PostHandshakeControls#

silk
PostHandshakeControls

Consecutive post-handshake control messages.

Tickets#

silk
Tickets

NewSessionTicket count.

TicketBytes#

silk
TicketBytes

Aggregate encoded NewSessionTicket bytes.

Arithmetic#

silk
Arithmetic

Checked size arithmetic.

Alpn#

silk
Alpn

One ALPN protocol or list.

CertificateDecode#

silk
CertificateDecode

The configured certificate-decoder policy.

CertificateSanDecode#

silk
CertificateSanDecode

The configured certificate-SAN decoder policy.

CertificateIdentity#

silk
CertificateIdentity

The configured HTTPS identity policy.

CertificatePath#

silk
CertificatePath

The configured certificate-path search policy.

CertificateProfile#

silk
CertificateProfile

The configured semantic certificate-profile policy.

Implementation Debug for TlsLimitKind#

silk
impl Debug for TlsLimitKind

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as HandshakeBodyBytes.

CertificateIdentityFailure#

silk
pub union CertificateIdentityFailure

A certificate identity failure that preserves its originating actor.

Decode#

silk
Decode

The certificate SAN adapter rejected structure or a resource bound.

Field error#

silk
pub error: SanDecodeError

The complete SAN adapter failure.

Match#

silk
Match

The HTTPS reference matcher rejected the complete decoded identity set.

Field error#

silk
pub error: IdentityError

The complete HTTPS identity failure.

Implementation Debug for CertificateIdentityFailure#

silk
impl Debug for CertificateIdentityFailure

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant and its fields, such as Decode { ... }.

CertificateVerifyFailure#

silk
pub union CertificateVerifyFailure

A server CertificateVerify failure with its selected signature implementation.

Ecdsa#

silk
Ecdsa

ECDSA-P256/SHA-256 verification failed.

Field error#

silk
pub error: P256SignatureError

The exact primitive failure.

EcdsaP384#

silk
EcdsaP384

ECDSA-P384/SHA-384 verification failed.

Field error#

silk
pub error: P384SignatureError

The exact primitive failure.

Rsa#

silk
Rsa

RSA-PSS-RSAE/SHA-256 verification failed.

Field error#

silk
pub error: RsaError

The exact primitive failure.

Implementation Debug for CertificateVerifyFailure#

silk
impl Debug for CertificateVerifyFailure

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant and its fields, such as Ecdsa { ... }.

TlsError#

silk
pub union TlsError

A typed TLS client failure: an alert from the server, a protocol error, a certificate that fails, a limit, or a call in the wrong state. An error is permanent for the client.

PeerAlert#

silk
PeerAlert

The peer sent a fatal alert with this wire description.

Field code#

silk
pub code: u8

The alert description byte.

BadRecordMac#

silk
BadRecordMac

Protected record authentication failed.

ProtocolViolation#

silk
ProtocolViolation

Peer syntax, ordering, or negotiation violated the selected TLS profile.

Field reason#

silk
pub reason: ProtocolReason

The rejected rule.

CertificateDecode#

silk
CertificateDecode

A peer certificate did not decode.

Field error#

silk
pub error: DecodeError

The complete strict decoder failure.

CertificatePath#

silk
CertificatePath

Certificate-path construction or validation failed.

Field error#

silk
pub error: ValidationError

The complete bounded path failure.

CertificateIdentity#

silk
CertificateIdentity

SAN decoding or HTTPS reference matching failed.

Field error#

silk
pub error: CertificateIdentityFailure

The exact nested identity failure.

CertificateVerify#

silk
CertificateVerify

Server CertificateVerify did not authenticate the transcript with the selected leaf key.

Field error#

silk
pub error: CertificateVerifyFailure

The exact selected primitive failure.

Finished#

silk
Finished

Server Finished did not authenticate the transcript.

UnsupportedProfile#

silk
UnsupportedProfile

A server-selected suite, group, signature, or extension shape is outside the selected profile.

LimitExceeded#

silk
LimitExceeded

A finite resource budget was exceeded.

Field kind#

silk
pub kind: TlsLimitKind

The exhausted resource category.

Field limit#

silk
pub limit: usize

The inclusive configured limit.

InvalidState#

silk
InvalidState

A caller operation is invalid in the current state.

Field operation#

silk
pub operation: ClientOperation

The rejected operation.

EmptyBuffer#

silk
EmptyBuffer

A caller supplied an empty plaintext output buffer.

HandshakeTruncated#

silk
HandshakeTruncated

Transport input ended before authentication and peer closure.

Truncated#

silk
Truncated

Transport input ended after authentication without peer close_notify.

NoApplicationProtocol#

silk
NoApplicationProtocol

The server omits a required ALPN selection or selects a protocol that was not offered.

Implementation Debug for TlsError#

silk
impl Debug for TlsError

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant and its fields, such as PeerAlert { ... }.

Implementation Copy for CertificateVerifyFailure#

silk
impl Copy for CertificateVerifyFailure

Authentication#

silk
pub struct Authentication<'a>

Authenticated result metadata borrowed from one client owner.

Implementation Authentication<'a>#

silk
impl<'a> Authentication<'a>

Method suite#

silk
pub fn suite(self: &Self) -> CipherSuite

Returns the negotiated TLS 1.3 cipher suite.

Method group#

silk
pub fn group(self: &Self) -> NamedGroup

Returns the negotiated ephemeral key-exchange group.

Method selectedAlpn#

silk
pub fn selectedAlpn(self: &Self) -> Option<&'a [u8]>

Returns the selected offered ALPN protocol, or None when ALPN was disabled or optional.

Method leafDer#

silk
pub fn leafDer(self: &Self) -> &'a [u8]

Borrows the exact DER of the leaf used for path, identity, and signature authentication.

Method intermediateIndices#

silk
pub fn intermediateIndices(self: &Self) -> &'a [usize]

Borrows selected intermediate indices in leaf-to-anchor order.

Method anchorIndex#

silk
pub fn anchorIndex(self: &Self) -> usize

Returns the selected explicit trust-anchor index.

Method validationTime#

silk
pub fn validationTime(self: &Self) -> Instant

Returns the exact caller-supplied validation instant, including nanoseconds.

Method revocationStatus#

silk
pub fn revocationStatus(self: &Self) -> RevocationStatus

Returns the revocation evidence produced by certificate-path validation.

Method sanIndex#

silk
pub fn sanIndex(self: &Self) -> usize

Returns the matching SAN's original certificate index.

Client#

silk
pub struct Client

A TLS 1.3 client state machine that does no I/O.

When to use#

Most programs use silk.tls.connection, which drives this client over a ByteDuplex. Use it directly when you need full control of the transport, for example in an event loop. Make it with Client.make, then move bytes between it and the transport until it reports Authenticated, and then read and write application data.

Details#

Each operation returns a Progress with a Demand that tells you what to do next. Output works in two steps: write Client.pendingOutput to the transport, and then call Client.ackWritten with the number of bytes written. The client does not keep your buffers. ClientLimits bound all memory and work.

It supports TLS 1.3 only, with the cipher suites ChaCha20-Poly1305, AES-128-GCM, and AES-256-GCM, the key exchange groups X25519 and P-256, and server keys ECDSA P-256, ECDSA P-384, and RSA (PSS with SHA-256). It checks the certificate chain against your TrustSnapshot, the host name of your ReferenceIdentity, the CertificateVerify signature, and the Finished message. It gives no application data before all checks pass.

Gotchas#

A TlsError is permanent for the client. Dropping the client does not send close_notify. Call Client.closeWrite first. Secrets are not erased.

Implementation Client#

silk
impl Client

Associated function make#

silk
pub effect fn make<'a>(
    config: &ClientConfig<'a>,
    trust: TrustSnapshot,
    validationTime: Instant,
  ) -> Result<Client, TlsError>
  ! OutOfMemoryError
  ? &mut Allocator | &mut Random

Returns a new client for config and trust, with the ClientHello already queued as pending output.

validationTime is the time used to check certificate dates. The client copies config and consumes trust. A DNS name is sent as SNI in lowercase. An IP address sends no SNI.

Errors#

  • Failure with a TlsError if config is not valid.

  • OutOfMemoryError if the Allocator fails.

Gotchas#

Give a correct current time and a secure Random provider. If Random fails, the program stops.

Method pendingOutput#

silk
pub fn pendingOutput<'a>(self: &'a Self) -> &'a [u8]

Returns the TLS output to send to the server. After you send some of it, call Client.ackWritten.

Method authentication#

silk
pub fn authentication<'a>(self: &'a Self) -> Option<Authentication<'a>>

Returns the result of authentication, after the Authenticated action. Before that, returns None.

Method feedInput#

silk
pub effect fn feedInput(
    self: &mut Self,
    input: &[u8],
  ) -> Result<Progress, TlsError>
  ! OutOfMemoryError
  ? &mut Allocator | &mut Random

Gives bytes from the server to the client. consumed in the result tells how many it accepted.

Details#

The client does not keep input. Give the bytes that it did not accept again in the next call. The client processes all complete handshake messages before it asks for more input. After the server sends close_notify, the client accepts and ignores all later bytes.

Method progress#

silk
pub effect fn progress(self: &mut Self) -> Result<Progress, TlsError>
  ! OutOfMemoryError
  ? &mut Allocator | &mut Random

Processes buffered handshake messages without new input, and returns the next action.

Method ackWritten#

silk
pub fn ackWritten(self: &mut Self, count: usize) -> Result<Progress, TlsError>

Tells the client that the transport sent the first count bytes of Client.pendingOutput.

Method readPlaintext#

silk
pub fn readPlaintext(
    self: &mut Self,
    output: &mut [u8],
  ) -> Result<Progress, TlsError>

Copies decrypted application data into output. produced in the result tells how many bytes.

Gotchas#

An empty output returns the EmptyBuffer error. It never means the end of input.

Method writePlaintext#

silk
pub fn writePlaintext(self: &mut Self, input: &[u8]) -> Result<Progress, TlsError>

Encrypts a prefix of input, at most 16384 bytes. consumed in the result tells how many bytes.

Details#

When TLS output is waiting, the client accepts no bytes. Send that output first. Do not give accepted bytes again, even if the transport sent only part of the record.

Method requestKeyUpdate#

silk
pub fn requestKeyUpdate(self: &mut Self) -> Result<Progress, TlsError>

Asks for a new local traffic key before the next application write. More calls before that write do nothing.

Method closeWrite#

silk
pub fn closeWrite(self: &mut Self) -> Result<Progress, TlsError>

Queues close_notify after the accepted output. More calls do nothing.

Method endInput#

silk
pub fn endInput(self: &mut Self) -> Result<Progress, TlsError>

Tells the client that the transport has no more input. Pending output stays pending.

Category: TLS and certificates.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.

View the source of this page