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#
TlsErrorfor 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.OutOfMemoryErrorfromClient.makeif theAllocatorfails.
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#
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#
pub enum NamedGroupOne key-exchange group selected for the authenticated connection.
X25519#
X25519X25519 with a 32-byte encoded public key.
P256#
P256The NIST P-256 curve with a 65-byte uncompressed encoded point.
AlpnProtocol#
pub struct AlpnProtocol<'a>One borrowed opaque ALPN protocol offered in caller order.
Field bytes#
pub bytes: &'a [u8]The nonempty protocol bytes, without a length prefix.
AlpnConfig#
pub union AlpnConfig<'a>Borrowed ALPN policy copied by Client.make.
Disabled#
DisabledSends no ALPN extension and accepts no server selection.
Offered#
OfferedSends one ordered, nonempty, duplicate-free protocol list.
Field protocols#
pub protocols: &'a [AlpnProtocol<'a>]The protocols in preference order.
Field required#
pub required: boolWhether absence of a server selection is a failure.
Implementation Copy for AlpnProtocol<'a>#
impl<'a> Copy for AlpnProtocol<'a>Implementation Copy for AlpnConfig<'a>#
impl<'a> Copy for AlpnConfig<'a>Implementation AlpnConfig<'a>#
impl<'a> AlpnConfig<'a>Associated function defaults#
pub fn defaults() -> AlpnConfig<'a>Returns the default policy, which sends no ALPN extension.
ClientLimits#
pub struct ClientLimitsInclusive local resource policy for one client connection. The limits bound all memory and work of the client.
Field handshakeBodyBytes#
pub handshakeBodyBytes: usizeMaximum body bytes in one handshake message.
Field handshakeBytes#
pub handshakeBytes: usizeMaximum total initial-handshake bytes, including four-byte headers.
Field handshakeMessages#
pub handshakeMessages: usizeMaximum initial-handshake message count.
Field peerCertificates#
pub peerCertificates: usizeMaximum peer certificate count, including the leaf.
Field certificateBytes#
pub certificateBytes: usizeMaximum DER bytes in one peer certificate.
Field certificateTotalBytes#
pub certificateTotalBytes: usizeMaximum aggregate peer DER bytes.
Field cookieBytes#
pub cookieBytes: usizeMaximum echoed HelloRetryRequest cookie bytes.
Field extensionBytes#
pub extensionBytes: usizeMaximum bytes in one extension vector.
Field emptyRecords#
pub emptyRecords: usizeMaximum consecutive empty application or compatibility records.
Field postHandshakeControls#
pub postHandshakeControls: usizeMaximum consecutive post-handshake control messages without positive application content.
Field tickets#
pub tickets: usizeMaximum accepted NewSessionTicket messages.
Field ticketBytes#
pub ticketBytes: usizeMaximum total encoded NewSessionTicket bytes.
Field certificateDecode#
pub certificateDecode: DecodeLimitsStrict certificate decoder policy.
Field sanDecode#
pub sanDecode: SanDecodeLimitsStrict SAN adapter policy.
Field identity#
pub identity: IdentityLimitsHTTPS identity policy.
Field path#
pub path: ValidationLimitsCertificate-path policy.
Implementation Copy for ClientLimits#
impl Copy for ClientLimitsImplementation ClientLimits#
impl ClientLimitsAssociated function defaults#
pub fn defaults() -> ClientLimitsReturns the finite TLS client defaults.
ClientConfig#
pub struct ClientConfig<'a>Borrowed client configuration that construction copies into owned storage.
Field reference#
pub reference: ReferenceIdentity<'a>A reference already admitted by HttpsIdentity.reference. Construction revalidates it.
Field alpn#
pub alpn: AlpnConfig<'a>ALPN policy, with opaque protocols in caller preference order.
Field limits#
pub limits: ClientLimitsFinite connection limits and nested certificate policies.
Demand#
pub enum DemandThe next action or observable connection event of a Client, in Progress.demand.
NeedInput#
NeedInputSupply peer transport bytes.
NeedOutput#
NeedOutputWrite and acknowledge the complete pending output suffix before other work.
PlaintextReady#
PlaintextReadyRead authenticated buffered application plaintext.
Authenticated#
AuthenticatedObserve the one-shot authentication transition and its metadata.
PeerClosed#
PeerClosedThe peer receive direction closed cleanly.
Closed#
ClosedBoth directions closed cleanly and no output remains.
Progress#
pub struct ProgressExact progress from one Client operation: how many bytes it took or wrote, and the next
Demand.
Field consumed#
pub consumed: usizeThe accepted prefix of caller input or application plaintext.
Field written#
pub written: usizeThe bytes copied into a caller plaintext destination.
Field demand#
pub demand: DemandThe next action or event under the fixed precedence rule.
ClientOperation#
pub enum ClientOperationA public operation rejected because of caller state.
FeedInput#
FeedInputTransport input feed.
Progress#
ProgressState-machine progress without new bytes.
AckWritten#
AckWrittenPending-output acknowledgment.
ReadPlaintext#
ReadPlaintextApplication plaintext read.
WritePlaintext#
WritePlaintextApplication plaintext write.
RequestKeyUpdate#
RequestKeyUpdateLocal traffic-key update request.
CloseWrite#
CloseWriteLocal write-direction close.
EndInput#
EndInputTransport input end.
Implementation Debug for ClientOperation#
impl Debug for ClientOperationOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as FeedInput.
ProtocolReason#
pub enum ProtocolReasonA protocol grammar or state-machine rule rejected from the peer.
Record#
RecordA record has an invalid content type, version, length, padding, or inner framing.
UnexpectedMessage#
UnexpectedMessageA handshake message arrived in an invalid order.
InvalidLength#
InvalidLengthA handshake message or vector is truncated or has trailing bytes.
Extension#
ExtensionAn extension is duplicated, unsolicited, misplaced, or malformed.
ServerHello#
ServerHelloServerHello does not select the admitted TLS 1.3 profile.
HelloRetryRequest#
HelloRetryRequestHelloRetryRequest violates its single-retry and changed-ClientHello rules.
CompatibilityCcs#
CompatibilityCcsCompatibility CCS is outside its window or not exactly {1}.
CertificateRequest#
CertificateRequestA CertificateRequest has invalid framing, duplication, or a profile-forbidden extension.
Certificate#
CertificateA peer Certificate message violates its TLS envelope.
PostHandshake#
PostHandshakeA NewSessionTicket or KeyUpdate message violates its TLS envelope.
InternalState#
InternalStateThe client reached an impossible private transition.
Implementation Debug for ProtocolReason#
impl Debug for ProtocolReasonOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as Record.
TlsLimitKind#
pub enum TlsLimitKindA named finite resource exhausted by peer input or configuration.
HandshakeBodyBytes#
HandshakeBodyBytesOne handshake body.
HandshakeBytes#
HandshakeBytesAggregate initial-handshake headers and bodies.
HandshakeMessages#
HandshakeMessagesInitial-handshake message count.
PeerCertificates#
PeerCertificatesPeer certificate count.
CertificateBytes#
CertificateBytesDER bytes in one peer certificate.
CertificateTotalBytes#
CertificateTotalBytesAggregate peer certificate DER bytes.
CookieBytes#
CookieBytesHelloRetryRequest cookie bytes.
ExtensionBytes#
ExtensionBytesOne extension vector.
EmptyRecords#
EmptyRecordsConsecutive empty application or compatibility records.
PostHandshakeControls#
PostHandshakeControlsConsecutive post-handshake control messages.
Tickets#
TicketsNewSessionTicket count.
TicketBytes#
TicketBytesAggregate encoded NewSessionTicket bytes.
Arithmetic#
ArithmeticChecked size arithmetic.
Alpn#
AlpnOne ALPN protocol or list.
CertificateDecode#
CertificateDecodeThe configured certificate-decoder policy.
CertificateSanDecode#
CertificateSanDecodeThe configured certificate-SAN decoder policy.
CertificateIdentity#
CertificateIdentityThe configured HTTPS identity policy.
CertificatePath#
CertificatePathThe configured certificate-path search policy.
CertificateProfile#
CertificateProfileThe configured semantic certificate-profile policy.
Implementation Debug for TlsLimitKind#
impl Debug for TlsLimitKindOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as HandshakeBodyBytes.
CertificateIdentityFailure#
pub union CertificateIdentityFailureA certificate identity failure that preserves its originating actor.
Decode#
DecodeThe certificate SAN adapter rejected structure or a resource bound.
Field error#
pub error: SanDecodeErrorThe complete SAN adapter failure.
Match#
MatchThe HTTPS reference matcher rejected the complete decoded identity set.
Field error#
pub error: IdentityErrorThe complete HTTPS identity failure.
Implementation Debug for CertificateIdentityFailure#
impl Debug for CertificateIdentityFailureOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant and its fields, such as Decode { ... }.
CertificateVerifyFailure#
pub union CertificateVerifyFailureA server CertificateVerify failure with its selected signature implementation.
Ecdsa#
EcdsaECDSA-P256/SHA-256 verification failed.
Field error#
pub error: P256SignatureErrorThe exact primitive failure.
EcdsaP384#
EcdsaP384ECDSA-P384/SHA-384 verification failed.
Field error#
pub error: P384SignatureErrorThe exact primitive failure.
Rsa#
RsaRSA-PSS-RSAE/SHA-256 verification failed.
Field error#
pub error: RsaErrorThe exact primitive failure.
Implementation Debug for CertificateVerifyFailure#
impl Debug for CertificateVerifyFailureOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant and its fields, such as Ecdsa { ... }.
TlsError#
pub union TlsErrorA 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#
PeerAlertThe peer sent a fatal alert with this wire description.
Field code#
pub code: u8The alert description byte.
BadRecordMac#
BadRecordMacProtected record authentication failed.
ProtocolViolation#
ProtocolViolationPeer syntax, ordering, or negotiation violated the selected TLS profile.
Field reason#
pub reason: ProtocolReasonThe rejected rule.
CertificateDecode#
CertificateDecodeA peer certificate did not decode.
Field error#
pub error: DecodeErrorThe complete strict decoder failure.
CertificatePath#
CertificatePathCertificate-path construction or validation failed.
Field error#
pub error: ValidationErrorThe complete bounded path failure.
CertificateIdentity#
CertificateIdentitySAN decoding or HTTPS reference matching failed.
Field error#
pub error: CertificateIdentityFailureThe exact nested identity failure.
CertificateVerify#
CertificateVerifyServer CertificateVerify did not authenticate the transcript with the selected leaf key.
Field error#
pub error: CertificateVerifyFailureThe exact selected primitive failure.
Finished#
FinishedServer Finished did not authenticate the transcript.
UnsupportedProfile#
UnsupportedProfileA server-selected suite, group, signature, or extension shape is outside the selected profile.
LimitExceeded#
LimitExceededA finite resource budget was exceeded.
Field kind#
pub kind: TlsLimitKindThe exhausted resource category.
Field limit#
pub limit: usizeThe inclusive configured limit.
InvalidState#
InvalidStateA caller operation is invalid in the current state.
Field operation#
pub operation: ClientOperationThe rejected operation.
EmptyBuffer#
EmptyBufferA caller supplied an empty plaintext output buffer.
HandshakeTruncated#
HandshakeTruncatedTransport input ended before authentication and peer closure.
Truncated#
TruncatedTransport input ended after authentication without peer close_notify.
NoApplicationProtocol#
NoApplicationProtocolThe server omits a required ALPN selection or selects a protocol that was not offered.
Implementation Debug for TlsError#
impl Debug for TlsErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant and its fields, such as PeerAlert { ... }.
Implementation Copy for CertificateVerifyFailure#
impl Copy for CertificateVerifyFailureAuthentication#
pub struct Authentication<'a>Authenticated result metadata borrowed from one client owner.
Implementation Authentication<'a>#
impl<'a> Authentication<'a>Method suite#
pub fn suite(self: &Self) -> CipherSuiteReturns the negotiated TLS 1.3 cipher suite.
Method group#
pub fn group(self: &Self) -> NamedGroupReturns the negotiated ephemeral key-exchange group.
Method selectedAlpn#
pub fn selectedAlpn(self: &Self) -> Option<&'a [u8]>Returns the selected offered ALPN protocol, or None when ALPN was disabled or optional.
Method leafDer#
pub fn leafDer(self: &Self) -> &'a [u8]Borrows the exact DER of the leaf used for path, identity, and signature authentication.
Method intermediateIndices#
pub fn intermediateIndices(self: &Self) -> &'a [usize]Borrows selected intermediate indices in leaf-to-anchor order.
Method anchorIndex#
pub fn anchorIndex(self: &Self) -> usizeReturns the selected explicit trust-anchor index.
Method validationTime#
pub fn validationTime(self: &Self) -> InstantReturns the exact caller-supplied validation instant, including nanoseconds.
Method revocationStatus#
pub fn revocationStatus(self: &Self) -> RevocationStatusReturns the revocation evidence produced by certificate-path validation.
Method sanIndex#
pub fn sanIndex(self: &Self) -> usizeReturns the matching SAN's original certificate index.
Client#
pub struct ClientA 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#
impl ClientAssociated function make#
pub effect fn make<'a>(
config: &ClientConfig<'a>,
trust: TrustSnapshot,
validationTime: Instant,
) -> Result<Client, TlsError>
! OutOfMemoryError
? &mut Allocator | &mut RandomReturns 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#
Failurewith aTlsErrorifconfigis not valid.OutOfMemoryErrorif theAllocatorfails.
Gotchas#
Give a correct current time and a secure Random provider. If Random fails, the program
stops.
Method pendingOutput#
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#
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#
pub effect fn feedInput(
self: &mut Self,
input: &[u8],
) -> Result<Progress, TlsError>
! OutOfMemoryError
? &mut Allocator | &mut RandomGives 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#
pub effect fn progress(self: &mut Self) -> Result<Progress, TlsError>
! OutOfMemoryError
? &mut Allocator | &mut RandomProcesses buffered handshake messages without new input, and returns the next action.
Method ackWritten#
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#
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#
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#
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#
pub fn closeWrite(self: &mut Self) -> Result<Progress, TlsError>Queues close_notify after the accepted output. More calls do nothing.
Method endInput#
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.