const std = @import("../std.zig");
const Tls = @This();
const net = std.net;
const mem = std.mem;
const crypto = std.crypto;
const assert = std.debug.assert;
pub const Client = @import("tls/Client.zig");
pub const record_header_len = 5;
pub const max_ciphertext_len = (1 << 14) + 256;
pub const max_ciphertext_record_len = max_ciphertext_len + record_header_len;
pub const hello_retry_request_sequence = [32]u8{
0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91,
0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C,
};
pub const close_notify_alert = [_]u8{
@enumToInt(AlertLevel.warning),
@enumToInt(AlertDescription.close_notify),
};
pub const ProtocolVersion = enum(u16) {
tls_1_2 = 0x0303,
tls_1_3 = 0x0304,
_,
};
pub const ContentType = enum(u8) {
invalid = 0,
change_cipher_spec = 20,
alert = 21,
handshake = 22,
application_data = 23,
_,
};
pub const HandshakeType = enum(u8) {
client_hello = 1,
server_hello = 2,
new_session_ticket = 4,
end_of_early_data = 5,
encrypted_extensions = 8,
certificate = 11,
certificate_request = 13,
certificate_verify = 15,
finished = 20,
key_update = 24,
message_hash = 254,
_,
};
pub const ExtensionType = enum(u16) {
server_name = 0,
max_fragment_length = 1,
status_request = 5,
supported_groups = 10,
signature_algorithms = 13,
use_srtp = 14,
heartbeat = 15,
application_layer_protocol_negotiation = 16,
signed_certificate_timestamp = 18,
client_certificate_type = 19,
server_certificate_type = 20,
padding = 21,
pre_shared_key = 41,
early_data = 42,
supported_versions = 43,
cookie = 44,
psk_key_exchange_modes = 45,
certificate_authorities = 47,
oid_filters = 48,
post_handshake_auth = 49,
signature_algorithms_cert = 50,
key_share = 51,
_,
};
pub const AlertLevel = enum(u8) {
warning = 1,
fatal = 2,
_,
};
pub const AlertDescription = enum(u8) {
close_notify = 0,
unexpected_message = 10,
bad_record_mac = 20,
record_overflow = 22,
handshake_failure = 40,
bad_certificate = 42,
unsupported_certificate = 43,
certificate_revoked = 44,
certificate_expired = 45,
certificate_unknown = 46,
illegal_parameter = 47,
unknown_ca = 48,
access_denied = 49,
decode_error = 50,
decrypt_error = 51,
protocol_version = 70,
insufficient_security = 71,
internal_error = 80,
inappropriate_fallback = 86,
user_canceled = 90,
missing_extension = 109,
unsupported_extension = 110,
unrecognized_name = 112,
bad_certificate_status_response = 113,
unknown_psk_identity = 115,
certificate_required = 116,
no_application_protocol = 120,
_,
};
pub const SignatureScheme = enum(u16) {
rsa_pkcs1_sha256 = 0x0401,
rsa_pkcs1_sha384 = 0x0501,
rsa_pkcs1_sha512 = 0x0601,
ecdsa_secp256r1_sha256 = 0x0403,
ecdsa_secp384r1_sha384 = 0x0503,
ecdsa_secp521r1_sha512 = 0x0603,
rsa_pss_rsae_sha256 = 0x0804,
rsa_pss_rsae_sha384 = 0x0805,
rsa_pss_rsae_sha512 = 0x0806,
ed25519 = 0x0807,
ed448 = 0x0808,
rsa_pss_pss_sha256 = 0x0809,
rsa_pss_pss_sha384 = 0x080a,
rsa_pss_pss_sha512 = 0x080b,
rsa_pkcs1_sha1 = 0x0201,
ecdsa_sha1 = 0x0203,
_,
};
pub const NamedGroup = enum(u16) {
secp256r1 = 0x0017,
secp384r1 = 0x0018,
secp521r1 = 0x0019,
x25519 = 0x001D,
x448 = 0x001E,
ffdhe2048 = 0x0100,
ffdhe3072 = 0x0101,
ffdhe4096 = 0x0102,
ffdhe6144 = 0x0103,
ffdhe8192 = 0x0104,
x25519_kyber512d00 = 0xFE30,
x25519_kyber768d00 = 0xFE31,
_,
};
pub const CipherSuite = enum(u16) {
AES_128_GCM_SHA256 = 0x1301,
AES_256_GCM_SHA384 = 0x1302,
CHACHA20_POLY1305_SHA256 = 0x1303,
AES_128_CCM_SHA256 = 0x1304,
AES_128_CCM_8_SHA256 = 0x1305,
AEGIS_256_SHA384 = 0x1306,
AEGIS_128L_SHA256 = 0x1307,
_,
};
pub const CertificateType = enum(u8) {
X509 = 0,
RawPublicKey = 2,
_,
};
pub const KeyUpdateRequest = enum(u8) {
update_not_requested = 0,
update_requested = 1,
_,
};
pub fn HandshakeCipherT(comptime AeadType: type, comptime HashType: type) type {
return struct {
pub const AEAD = AeadType;
pub const Hash = HashType;
pub const Hmac = crypto.auth.hmac.Hmac(Hash);
pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
handshake_secret: [Hkdf.prk_length]u8,
master_secret: [Hkdf.prk_length]u8,
client_handshake_key: [AEAD.key_length]u8,
server_handshake_key: [AEAD.key_length]u8,
client_finished_key: [Hmac.key_length]u8,
server_finished_key: [Hmac.key_length]u8,
client_handshake_iv: [AEAD.nonce_length]u8,
server_handshake_iv: [AEAD.nonce_length]u8,
transcript_hash: Hash,
};
}
pub const HandshakeCipher = union(enum) {
AES_128_GCM_SHA256: HandshakeCipherT(crypto.aead.aes_gcm.Aes128Gcm, crypto.hash.sha2.Sha256),
AES_256_GCM_SHA384: HandshakeCipherT(crypto.aead.aes_gcm.Aes256Gcm, crypto.hash.sha2.Sha384),
CHACHA20_POLY1305_SHA256: HandshakeCipherT(crypto.aead.chacha_poly.ChaCha20Poly1305, crypto.hash.sha2.Sha256),
AEGIS_256_SHA384: HandshakeCipherT(crypto.aead.aegis.Aegis256, crypto.hash.sha2.Sha384),
AEGIS_128L_SHA256: HandshakeCipherT(crypto.aead.aegis.Aegis128L, crypto.hash.sha2.Sha256),
};
pub fn ApplicationCipherT(comptime AeadType: type, comptime HashType: type) type {
return struct {
pub const AEAD = AeadType;
pub const Hash = HashType;
pub const Hmac = crypto.auth.hmac.Hmac(Hash);
pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
client_secret: [Hash.digest_length]u8,
server_secret: [Hash.digest_length]u8,
client_key: [AEAD.key_length]u8,
server_key: [AEAD.key_length]u8,
client_iv: [AEAD.nonce_length]u8,
server_iv: [AEAD.nonce_length]u8,
};
}
pub const ApplicationCipher = union(enum) {
AES_128_GCM_SHA256: ApplicationCipherT(crypto.aead.aes_gcm.Aes128Gcm, crypto.hash.sha2.Sha256),
AES_256_GCM_SHA384: ApplicationCipherT(crypto.aead.aes_gcm.Aes256Gcm, crypto.hash.sha2.Sha384),
CHACHA20_POLY1305_SHA256: ApplicationCipherT(crypto.aead.chacha_poly.ChaCha20Poly1305, crypto.hash.sha2.Sha256),
AEGIS_256_SHA384: ApplicationCipherT(crypto.aead.aegis.Aegis256, crypto.hash.sha2.Sha384),
AEGIS_128L_SHA256: ApplicationCipherT(crypto.aead.aegis.Aegis128L, crypto.hash.sha2.Sha256),
};
pub fn hkdfExpandLabel(
comptime Hkdf: type,
key: [Hkdf.prk_length]u8,
label: []const u8,
context: []const u8,
comptime len: usize,
) [len]u8 {
const max_label_len = 255;
const max_context_len = 255;
const tls13 = "tls13 ";
var buf: [2 + 1 + tls13.len + max_label_len + 1 + max_context_len]u8 = undefined;
mem.writeIntBig(u16, buf[0..2], len);
buf[2] = @intCast(u8, tls13.len + label.len);
buf[3..][0..tls13.len].* = tls13.*;
var i: usize = 3 + tls13.len;
mem.copy(u8, buf[i..], label);
i += label.len;
buf[i] = @intCast(u8, context.len);
i += 1;
mem.copy(u8, buf[i..], context);
i += context.len;
var result: [len]u8 = undefined;
Hkdf.expand(&result, buf[0..i], key);
return result;
}
pub fn emptyHash(comptime Hash: type) [Hash.digest_length]u8 {
var result: [Hash.digest_length]u8 = undefined;
Hash.hash(&.{}, &result, .{});
return result;
}
pub fn hmac(comptime Hmac: type, message: []const u8, key: [Hmac.key_length]u8) [Hmac.mac_length]u8 {
var result: [Hmac.mac_length]u8 = undefined;
Hmac.create(&result, message, &key);
return result;
}
pub inline fn extension(comptime et: ExtensionType, bytes: anytype) [2 + 2 + bytes.len]u8 {
return int2(@enumToInt(et)) ++ array(1, bytes);
}
pub inline fn array(comptime elem_size: comptime_int, bytes: anytype) [2 + bytes.len]u8 {
comptime assert(bytes.len % elem_size == 0);
return int2(bytes.len) ++ bytes;
}
pub inline fn enum_array(comptime E: type, comptime tags: []const E) [2 + @sizeOf(E) * tags.len]u8 {
assert(@sizeOf(E) == 2);
var result: [tags.len * 2]u8 = undefined;
for (tags, 0..) |elem, i| {
result[i * 2] = @truncate(u8, @enumToInt(elem) >> 8);
result[i * 2 + 1] = @truncate(u8, @enumToInt(elem));
}
return array(2, result);
}
pub inline fn int2(x: u16) [2]u8 {
return .{
@truncate(u8, x >> 8),
@truncate(u8, x),
};
}
pub inline fn int3(x: u24) [3]u8 {
return .{
@truncate(u8, x >> 16),
@truncate(u8, x >> 8),
@truncate(u8, x),
};
}
pub const Decoder = struct {
buf: []u8,
idx: usize = 0,
our_end: usize = 0,
their_end: usize = 0,
cap: usize = 0,
disable_reads: bool = false,
pub fn fromTheirSlice(buf: []u8) Decoder {
return .{
.buf = buf,
.their_end = buf.len,
.cap = buf.len,
.disable_reads = true,
};
}
pub fn readAtLeast(d: *Decoder, stream: anytype, their_amt: usize) !void {
assert(!d.disable_reads);
const existing_amt = d.cap - d.idx;
d.their_end = d.idx + their_amt;
if (their_amt <= existing_amt) return;
const request_amt = their_amt - existing_amt;
const dest = d.buf[d.cap..];
if (request_amt > dest.len) return error.TlsRecordOverflow;
const actual_amt = try stream.readAtLeast(dest, request_amt);
if (actual_amt < request_amt) return error.TlsConnectionTruncated;
d.cap += actual_amt;
}
pub fn readAtLeastOurAmt(d: *Decoder, stream: anytype, our_amt: usize) !void {
assert(!d.disable_reads);
try readAtLeast(d, stream, our_amt);
d.our_end = d.idx + our_amt;
}
pub fn ensure(d: *Decoder, amt: usize) !void {
d.our_end = @max(d.idx + amt, d.our_end);
if (d.our_end > d.their_end) return error.TlsDecodeError;
}
pub fn decode(d: *Decoder, comptime T: type) T {
switch (@typeInfo(T)) {
.Int => |info| switch (info.bits) {
8 => {
skip(d, 1);
return d.buf[d.idx - 1];
},
16 => {
skip(d, 2);
const b0: u16 = d.buf[d.idx - 2];
const b1: u16 = d.buf[d.idx - 1];
return (b0 << 8) | b1;
},
24 => {
skip(d, 3);
const b0: u24 = d.buf[d.idx - 3];
const b1: u24 = d.buf[d.idx - 2];
const b2: u24 = d.buf[d.idx - 1];
return (b0 << 16) | (b1 << 8) | b2;
},
else => @compileError("unsupported int type: " ++ @typeName(T)),
},
.Enum => |info| {
const int = d.decode(info.tag_type);
if (info.is_exhaustive) @compileError("exhaustive enum cannot be used");
return @intToEnum(T, int);
},
else => @compileError("unsupported type: " ++ @typeName(T)),
}
}
pub fn array(d: *Decoder, comptime len: usize) *[len]u8 {
skip(d, len);
return d.buf[d.idx - len ..][0..len];
}
pub fn slice(d: *Decoder, len: usize) []u8 {
skip(d, len);
return d.buf[d.idx - len ..][0..len];
}
pub fn skip(d: *Decoder, amt: usize) void {
d.idx += amt;
assert(d.idx <= d.our_end);
}
pub fn eof(d: Decoder) bool {
assert(d.our_end <= d.their_end);
assert(d.idx <= d.our_end);
return d.idx == d.their_end;
}
pub fn sub(d: *Decoder, their_len: usize) !Decoder {
const end = d.idx + their_len;
if (end > d.their_end) return error.TlsDecodeError;
const sub_buf = d.buf[d.idx..end];
d.idx = end;
d.our_end = end;
return fromTheirSlice(sub_buf);
}
pub fn rest(d: Decoder) []u8 {
return d.buf[d.idx..d.cap];
}
};