readIndirect
Function parameters
Parameters
- c:*Client
Type definitions in this namespace
Types
Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session.
Functions
Error sets in this namespace
Error Sets
The `Reader` supplied to `init` requires a buffer capacity
Values
- min_buffer_len
- The `Reader` supplied to `init` requires a buffer capacity
Source
Implementation
fn readIndirect(c: *Client) Reader.Error!usize {
const r = &c.reader;
if (c.eof()) return error.EndOfStream;
const input = c.input;
// If at least one full encrypted record is not buffered, read once.
const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {
error.EndOfStream => {
// This is either a truncation attack, a bug in the server, or an
// intentional omission of the close_notify message due to truncation
// detection handled above the TLS layer.
if (c.allow_truncation_attacks) {
c.received_close_notify = true;
return error.EndOfStream;
} else {
return failRead(c, error.TlsConnectionTruncated);
}
},
error.ReadFailed => return error.ReadFailed,
};
const ct: tls.ContentType = @enumFromInt(record_header[0]);
const legacy_version = mem.readInt(u16, record_header[1..][0..2], .big);
_ = legacy_version;
const record_len = mem.readInt(u16, record_header[3..][0..2], .big);
if (record_len > max_ciphertext_len) return failRead(c, error.TlsRecordOverflow);
const record_end = 5 + record_len;
if (record_end > input.buffered().len) {
input.fillMore() catch |err| switch (err) {
error.EndOfStream => return failRead(c, error.TlsConnectionTruncated),
error.ReadFailed => return error.ReadFailed,
};
if (record_end > input.buffered().len) return 0;
}
const cleartext_len, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) {
inline else => |*p| switch (c.tls_version) {
.tls_1_3 => {
const pv = &p.tls_1_3;
const P = @TypeOf(p.*);
const ad = input.take(tls.record_header_len) catch unreachable; // already peeked
const ciphertext_len = record_len - P.AEAD.tag_length;
const ciphertext = input.take(ciphertext_len) catch unreachable; // already peeked
const auth_tag = (input.takeArray(P.AEAD.tag_length) catch unreachable).*; // already peeked
const nonce = nonce: {
const V = @Vector(P.AEAD.nonce_length, u8);
const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
const operand: V = pad ++ mem.toBytes(big(c.read_seq));
break :nonce @as(V, pv.server_iv) ^ operand;
};
rebase(r, ciphertext.len);
const cleartext = r.buffer[r.end..][0..ciphertext.len];
P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch
return failRead(c, error.TlsBadRecordMac);
// TODO use scalar, non-slice version
const msg = mem.trimRight(u8, cleartext, "\x00");
break :cleartext .{ msg.len - 1, @enumFromInt(msg[msg.len - 1]) };
},
.tls_1_2 => {
const pv = &p.tls_1_2;
const P = @TypeOf(p.*);
const message_len: u16 = record_len - P.record_iv_length - P.mac_length;
const ad_header = input.take(tls.record_header_len) catch unreachable; // already peeked
const ad = mem.toBytes(big(c.read_seq)) ++
ad_header[0 .. 1 + 2] ++
mem.toBytes(big(message_len));
const record_iv = (input.takeArray(P.record_iv_length) catch unreachable).*; // already peeked
const masked_read_seq = c.read_seq &
comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);
const nonce: [P.AEAD.nonce_length]u8 = nonce: {
const V = @Vector(P.AEAD.nonce_length, u8);
const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
const operand: V = pad ++ @as([8]u8, @bitCast(big(masked_read_seq)));
break :nonce @as(V, pv.server_write_IV ++ record_iv) ^ operand;
};
const ciphertext = input.take(message_len) catch unreachable; // already peeked
const auth_tag = (input.takeArray(P.mac_length) catch unreachable).*; // already peeked
rebase(r, ciphertext.len);
const cleartext = r.buffer[r.end..][0..ciphertext.len];
P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch
return failRead(c, error.TlsBadRecordMac);
break :cleartext .{ cleartext.len, ct };
},
else => unreachable,
},
};
const cleartext = r.buffer[r.end..][0..cleartext_len];
c.read_seq = std.math.add(u64, c.read_seq, 1) catch return failRead(c, error.TlsSequenceOverflow);
switch (inner_ct) {
.alert => {
if (cleartext.len != 2) return failRead(c, error.TlsDecodeError);
const alert: tls.Alert = .{
.level = @enumFromInt(cleartext[0]),
.description = @enumFromInt(cleartext[1]),
};
switch (alert.description) {
.close_notify => {
c.received_close_notify = true;
return 0;
},
.user_canceled => {
// TODO: handle server-side closures
return failRead(c, error.TlsUnexpectedMessage);
},
else => {
c.alert = alert;
return failRead(c, error.TlsAlert);
},
}
},
.handshake => {
var ct_i: usize = 0;
while (true) {
const handshake_type: tls.HandshakeType = @enumFromInt(cleartext[ct_i]);
ct_i += 1;
const handshake_len = mem.readInt(u24, cleartext[ct_i..][0..3], .big);
ct_i += 3;
const next_handshake_i = ct_i + handshake_len;
if (next_handshake_i > cleartext.len) return failRead(c, error.TlsBadLength);
const handshake = cleartext[ct_i..next_handshake_i];
switch (handshake_type) {
.new_session_ticket => {
// This client implementation ignores new session tickets.
},
.key_update => {
switch (c.application_cipher) {
inline else => |*p| {
const pv = &p.tls_1_3;
const P = @TypeOf(p.*);
const server_secret = hkdfExpandLabel(P.Hkdf, pv.server_secret, "traffic upd", "", P.Hash.digest_length);
if (c.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
.counter = key_log.serverCounter(),
.client_random = &key_log.client_random,
}, .{
.SERVER_TRAFFIC_SECRET = &server_secret,
});
pv.server_secret = server_secret;
pv.server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);
pv.server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);
},
}
c.read_seq = 0;
switch (@as(tls.KeyUpdateRequest, @enumFromInt(handshake[0]))) {
.update_requested => {
switch (c.application_cipher) {
inline else => |*p| {
const pv = &p.tls_1_3;
const P = @TypeOf(p.*);
const client_secret = hkdfExpandLabel(P.Hkdf, pv.client_secret, "traffic upd", "", P.Hash.digest_length);
if (c.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
.counter = key_log.clientCounter(),
.client_random = &key_log.client_random,
}, .{
.CLIENT_TRAFFIC_SECRET = &client_secret,
});
pv.client_secret = client_secret;
pv.client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length);
pv.client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length);
},
}
c.write_seq = 0;
},
.update_not_requested => {},
_ => return failRead(c, error.TlsIllegalParameter),
}
},
else => return failRead(c, error.TlsUnexpectedMessage),
}
ct_i = next_handshake_i;
if (ct_i >= cleartext.len) break;
}
return 0;
},
.application_data => {
r.end += cleartext.len;
return 0;
},
else => return failRead(c, error.TlsUnexpectedMessage),
}
}