silk.tls.record.
Frame and protect TLS 1.3 records (RFC 8446, section 5) over a byte transport that you drive.
When to use#
Most programs use silk.tls.connection or silk.tls.client, which use this module. Use
TlsRecordSender and TlsRecordReceiver directly to build another TLS endpoint or a test
tool. The makePlaintext functions frame records without encryption, for the first handshake
messages. The make functions take a traffic secret and encrypt with the CipherSuite.
Details#
Each direction allocates fixed buffers one time, when you make it. Later calls do not allocate
and do not keep your buffers. The sender holds at most one encoded record: write
TlsRecordSender.pendingOutput to the transport, and then call TlsRecordSender.ackWritten
with the number of bytes that the transport took. The receiver takes input in pieces with
TlsRecordReceiver.feedInput, and gives you one complete, authenticated record at a time. A
traffic key protects at most RECORDS_PER_EPOCH records.
Errors#
RecordErrorfor a wrong secret length, a call in the wrong state, a record that is too large or not valid, a record that fails authentication, or a key that is used up.OutOfMemoryErrorfrom themakefunctions if theAllocatorfails.
Gotchas#
A traffic secret does not prove who the peer is. This module does not do the handshake, check
certificates, or decide when to send close_notify. An error from a peer record is permanent
for that receiver. Secrets are not erased.
Examples#
Send a record and receive it#
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
import silk.core.result { Result }
import silk.tls.record {
ContentType,
InputDemand,
InputProgress,
OutputProgress,
RecordError,
TlsRecordReceiver,
TlsRecordSender
}
pub effect fn main() -> i32
! OutOfMemoryError {
let mut allocator = Allocator.systemAllocatorProvider()
let mut sender = run TlsRecordSender.makePlaintext()
|> Effect.provideMut<Allocator>(&mut allocator)
let mut receiver = run TlsRecordReceiver.makePlaintext()
|> Effect.provideMut<Allocator>(&mut allocator)
match TlsRecordSender.queueRecord(&mut sender, ContentType.Handshake, b"hello") {
Result<OutputProgress, RecordError>.Success { value } => {
if value.accepted != 5 {
return 1
}
}
Result<OutputProgress, RecordError>.Failure { error: _ } => {
return 1
}
}
// The wire bytes are a 5-byte record header and the content.
let wire = TlsRecordSender.pendingOutput(&sender)
let length = wire.length
match TlsRecordReceiver.feedInput(&mut receiver, wire) {
Result<InputProgress, RecordError>.Success { value } => {
if value.consumed != 10 || value.demand != InputDemand.RecordReady {
return 1
}
}
Result<InputProgress, RecordError>.Failure { error: _ } => {
return 1
}
}
match TlsRecordSender.ackWritten(&mut sender, length) {
Result<(), RecordError>.Success { value: _ } => {}
Result<(), RecordError>.Failure { error: _ } => {
return 1
}
}
let mut content: [u8; 16] = [0; 16]
return match TlsRecordReceiver.consumeRecordInto(&mut receiver, &mut content) {
Result<usize, RecordError>.Success { value } => {
if value != 5 || content[0] != 104 {
return 1
}
return 0
}
Result<usize, RecordError>.Failure { error: _ } => 1
}
}Import as TlsRecordSender with import silk.tls.record { TlsRecordSender }.
Public declarations: 11.
RECORDS_PER_EPOCH#
pub const RECORDS_PER_EPOCH: u64The maximum number of records that one traffic key protects. After it, install a new key, for example after a KeyUpdate.
CipherSuite#
pub enum CipherSuiteA TLS 1.3 cipher suite that this module supports.
Aes128GcmSha256#
Aes128GcmSha256TLS_AES_128_GCM_SHA256. Its traffic secrets are 32 bytes.
Aes256GcmSha384#
Aes256GcmSha384TLS_AES_256_GCM_SHA384. Its traffic secrets are 48 bytes.
ChaCha20Poly1305Sha256#
ChaCha20Poly1305Sha256TLS_CHACHA20_POLY1305_SHA256. Its traffic secrets are 32 bytes.
ContentType#
pub enum ContentTypeThe content type of a record.
ChangeCipherSpec#
ChangeCipherSpecThe change_cipher_spec record that TLS 1.3 sends only for middlebox compatibility. Only the
plaintext form, with the content {1}, is accepted.
Alert#
AlertAn alert. The content is exactly 2 bytes.
Handshake#
HandshakeHandshake messages. The content is not empty.
ApplicationData#
ApplicationDataApplication data. The content can be empty.
InputDemand#
pub enum InputDemandWhat the receiver needs after TlsRecordReceiver.feedInput.
NeedInput#
NeedInputThe record is not complete. Give more bytes.
RecordReady#
RecordReadyA record is complete. Read it, and then consume it.
NeedRecordConsumption#
NeedRecordConsumptionA record was already ready, so no bytes were taken. Consume it first.
InputProgress#
pub struct InputProgressThe result of TlsRecordReceiver.feedInput.
Field consumed#
pub consumed: usizeThe number of input bytes that the receiver took. Give the rest again later.
Field demand#
pub demand: InputDemandWhat the receiver needs next.
OutputDemand#
pub enum OutputDemandWhat the sender needs after TlsRecordSender.queueRecord.
RecordQueued#
RecordQueuedA record is encoded. Write TlsRecordSender.pendingOutput to the transport.
NeedOutput#
NeedOutputAn earlier record is not fully written yet, so nothing was taken. Write and acknowledge it first.
OutputProgress#
pub struct OutputProgressThe result of TlsRecordSender.queueRecord.
Field accepted#
pub accepted: usizeThe number of content bytes in the queued record. At most 16384.
Field demand#
pub demand: OutputDemandWhat the sender needs next.
RecordError#
pub union RecordErrorThe reason that a record operation failed.
InvalidSecretLength#
InvalidSecretLengthThe traffic secret has the wrong length for the cipher suite.
Field requested#
pub requested: usizeThe supplied byte length.
Field expected#
pub expected: usizeThe required byte length.
InvalidState#
InvalidStateThe call is not valid now, for example a feed while a record is ready, or any call after a peer error.
RecordOverflow#
RecordOverflowA record, or the content to copy, is larger than the limit or the buffer.
Field requested#
pub requested: usizeThe rejected length.
Field maximum#
pub maximum: usizeThe largest admitted length.
InvalidContent#
InvalidContentA content type, version, inner plaintext, or alert is not valid.
AuthenticationFailed#
AuthenticationFailedA protected record failed decryption. It was changed, or the key is wrong.
InvalidAcknowledgment#
InvalidAcknowledgmentAn acknowledgment is larger than the pending output.
Field requested#
pub requested: usizeThe requested acknowledgment count.
Field pending#
pub pending: usizeThe number of bytes currently pending.
KeyUsageExhausted#
KeyUsageExhaustedThe traffic key protected RECORDS_PER_EPOCH records. Install a new key.
Implementation Debug for RecordError#
impl Debug for RecordErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant and its fields, such as InvalidSecretLength { ... }.
RecordView#
pub struct RecordView<'owner>A complete record from a receiver. It borrows the receiver until you consume the record.
Field contentType#
pub contentType: ContentTypeThe content type, after authentication.
Field content#
pub content: &'owner [u8]The content, without the header, the tag, the inner type, or the padding.
TlsRecordSender#
pub struct TlsRecordSenderThe send direction of a TLS record layer. It holds at most one encoded record.
Most programs use silk.tls.connection or silk.tls.client, which use the record layer. Use it
directly to build another TLS endpoint or a test tool. makePlaintext frames records without
encryption, for the first handshake messages. make takes a traffic secret and encrypts with
the CipherSuite.
It allocates a fixed buffer one time, when you make it. Write
TlsRecordSender.pendingOutput to the transport, and then call
TlsRecordSender.ackWritten with the number of bytes that the transport took. A traffic key
protects at most RECORDS_PER_EPOCH records.
The protected form derives the key and IV from the traffic secret and starts at sequence number 0. Records are not padded.
Gotchas#
There is no way to reset the sequence number. When the key is used up, install a new one with
TlsRecordSender.replaceEpoch.
Implementation TlsRecordSender#
impl TlsRecordSenderAssociated function make#
pub effect fn make(
suite: CipherSuite,
trafficSecret: &[u8],
) -> Result<TlsRecordSender, RecordError> ! OutOfMemoryError ? &mut AllocatorReturns a sender that encrypts with suite and trafficSecret.
The secret is 32 bytes for the SHA-256 suites and 48 bytes for the SHA-384 suite.
Errors#
FailurewithInvalidSecretLengthif the secret has the wrong length. Nothing is allocated.OutOfMemoryErrorif theAllocatorfails.
Associated function makePlaintext#
pub effect fn makePlaintext() -> TlsRecordSender ! OutOfMemoryError ? &mut AllocatorReturns a sender that frames records without encryption, for the first handshake messages.
Method replaceEpoch#
pub fn replaceEpoch(
self: &mut Self,
suite: CipherSuite,
trafficSecret: &[u8],
) -> Result<(), RecordError>Replaces the traffic key, for example after the handshake or a KeyUpdate. It does not allocate.
The sequence number starts at 0 again.
Errors#
InvalidStateif output is pending.InvalidSecretLengthif the secret has the wrong length.
On an error, the old key stays in use.
Method queueRecord#
pub fn queueRecord(
self: &mut Self,
contentType: ContentType,
content: &[u8],
) -> Result<OutputProgress, RecordError>Encodes one record with up to 16384 bytes of content, and returns how many bytes it took.
If a record is still pending, it takes nothing and returns NeedOutput. It does not keep
content.
Errors#
InvalidContentfor empty handshake content or an alert that is not 2 bytes.KeyUsageExhaustedif the key is used up.
Method pendingOutput#
pub fn pendingOutput<'owner>(self: &'owner Self) -> &'owner [u8]Returns the bytes of the pending record that are not written yet. It returns an empty slice when nothing is pending.
Method cancelPending#
pub fn cancelPending(self: &mut Self) -> ()Drops the pending record. The key and the sequence number do not change.
Gotchas#
If part of the record was written, the stream is broken. Close the transport.
Method recordsRemaining#
pub fn recordsRemaining(self: &Self) -> u64Returns the number of records that the current key can still protect, or 0 for a plaintext sender.
Method ackWritten#
pub fn ackWritten(self: &mut Self, count: usize) -> Result<(), RecordError>Marks the first count pending bytes as written.
Errors#
InvalidAcknowledgmentifcountis more than the pending bytes. Nothing changes.
TlsRecordReceiver#
pub struct TlsRecordReceiverThe receive direction of a TLS record layer. It holds at most one complete record.
Most programs use silk.tls.connection or silk.tls.client, which use the record layer. Use it
directly to build another TLS endpoint or a test tool. It allocates a fixed buffer one
time, when you make it, and takes input in pieces with TlsRecordReceiver.feedInput.
It checks each header before it copies the body, and it gives you a record only after authentication and all content checks.
Gotchas#
Any error in a peer record is permanent for this receiver. Do not use it again.
Implementation TlsRecordReceiver#
impl TlsRecordReceiverAssociated function make#
pub effect fn make(
suite: CipherSuite,
trafficSecret: &[u8],
) -> Result<TlsRecordReceiver, RecordError> ! OutOfMemoryError ? &mut AllocatorReturns a receiver that decrypts with suite and trafficSecret.
Errors#
FailurewithInvalidSecretLengthif the secret has the wrong length.OutOfMemoryErrorif theAllocatorfails.
Associated function makePlaintext#
pub effect fn makePlaintext() -> TlsRecordReceiver ! OutOfMemoryError ? &mut AllocatorReturns a receiver for records without encryption, for the first handshake messages.
Method replaceEpoch#
pub fn replaceEpoch(
self: &mut Self,
suite: CipherSuite,
trafficSecret: &[u8],
) -> Result<(), RecordError>Replaces the traffic key. It does not allocate. The sequence number starts at 0 again.
Errors#
InvalidStateif part of a record was received, a record is ready, or the receiver failed.InvalidSecretLengthif the secret has the wrong length.
On an error, the old key stays in use.
Method feedInput#
pub fn feedInput(self: &mut Self, input: &[u8]) -> Result<InputProgress, RecordError>Copies bytes of the current record from input, and returns how many it took and what it
needs next.
It stops at the end of a record, so give the rest of input again after you consume the
record. With an encrypted key, a plaintext change_cipher_spec record with the content {1}
is still accepted.
Errors#
RecordOverflow,InvalidContent, orAuthenticationFailedfor a bad record. These errors are permanent.
Method record#
pub fn record<'owner>(self: &'owner Self) -> Option<RecordView<'owner>>Returns the ready record, or None if no record is complete.
Method readyContentType#
pub fn readyContentType(self: &Self) -> Option<ContentType>Returns the content type of the ready record, or None.
Method readyLength#
pub fn readyLength(self: &Self) -> usizeReturns the content length of the ready record, or 0 if none is ready.
Method consumeRecordInto#
pub fn consumeRecordInto(
self: &mut Self,
destination: &mut [u8],
) -> Result<usize, RecordError>Copies the content of the ready record into destination, consumes the record, and returns
its length.
Errors#
RecordOverflowifdestinationis too small. The record stays ready.InvalidStateif no record is ready.
Method consumeRecord#
pub fn consumeRecord(self: &mut Self) -> Result<(), RecordError>Releases the ready record, so that the receiver can take the next one.
Errors#
InvalidStateif no record is ready.
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.