Function init [src]

Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session. host is only borrowed during this function call. input is asserted to have buffer capacity at least min_buffer_len.

Prototype

pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client

Parameters

input: *Readeroutput: *Writeroptions: Options

Possible Errors

BufferTooSmall
CertificateExpired
CertificateFieldHasInvalidLength
CertificateFieldHasWrongDataType
CertificateHasInvalidBitString
CertificateHasUnrecognizedObjectId
CertificateHostMismatch
CertificateIssuerMismatch
CertificateNotYetValid
CertificatePublicKeyInvalid
CertificateSignatureAlgorithmMismatch
CertificateSignatureAlgorithmUnsupported
CertificateSignatureInvalid
CertificateSignatureInvalidLength
CertificateSignatureNamedCurveUnsupported
CertificateSignatureUnsupportedBitCount
CertificateTimeInvalid
DiskQuota
IdentityElement
InsufficientEntropy
InvalidEncoding
InvalidSignature
LockViolation
MessageTooLong
NegativeIntoUnsigned
NonCanonical
NotOpenForWriting
NotSquare
ReadFailed
SignatureVerificationFailed
TargetTooSmall
TlsAlert

The alert description will be stored in alert.

TlsBadRecordMac
TlsBadRsaSignatureBitCount
TlsBadSignatureScheme
TlsCertificateNotVerified
TlsConnectionTruncated
TlsDecodeError
TlsDecryptError
TlsDecryptFailure
TlsIllegalParameter
TlsRecordOverflow
TlsUnexpectedMessage
UnsupportedCertificateVersion
WeakPublicKey
WriteFailed

Source

pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client { assert(input.buffer.len >= min_buffer_len); const host = switch (options.host) { .no_verification => "", .explicit => |host| host, }; const host_len: u16 = @intCast(host.len); var random_buffer: [176]u8 = undefined; crypto.random.bytes(&random_buffer); const client_hello_rand = random_buffer[0..32].*; var key_seq: u64 = 0; var server_hello_rand: [32]u8 = undefined; const legacy_session_id = random_buffer[32..64].*; var key_share = KeyShare.init(random_buffer[64..176].*) catch |err| switch (err) { // Only possible to happen if the seed is all zeroes. error.IdentityElement => return error.InsufficientEntropy, }; const extensions_payload = tls.extension(.supported_versions, array(u8, tls.ProtocolVersion, .{ .tls_1_3, .tls_1_2, })) ++ tls.extension(.signature_algorithms, array(u16, tls.SignatureScheme, .{ .ecdsa_secp256r1_sha256, .ecdsa_secp384r1_sha384, .rsa_pkcs1_sha256, .rsa_pkcs1_sha384, .rsa_pkcs1_sha512, .rsa_pss_rsae_sha256, .rsa_pss_rsae_sha384, .rsa_pss_rsae_sha512, .rsa_pss_pss_sha256, .rsa_pss_pss_sha384, .rsa_pss_pss_sha512, .rsa_pkcs1_sha1, .ed25519, })) ++ tls.extension(.supported_groups, array(u16, tls.NamedGroup, .{ .x25519_ml_kem768, .secp256r1, .secp384r1, .x25519, })) ++ tls.extension(.psk_key_exchange_modes, array(u8, tls.PskKeyExchangeMode, .{ .psk_dhe_ke, })) ++ tls.extension(.key_share, array( u16, u8, int(u16, @intFromEnum(tls.NamedGroup.x25519_ml_kem768)) ++ array(u16, u8, key_share.ml_kem768_kp.public_key.toBytes() ++ key_share.x25519_kp.public_key) ++ int(u16, @intFromEnum(tls.NamedGroup.secp256r1)) ++ array(u16, u8, key_share.secp256r1_kp.public_key.toUncompressedSec1()) ++ int(u16, @intFromEnum(tls.NamedGroup.secp384r1)) ++ array(u16, u8, key_share.secp384r1_kp.public_key.toUncompressedSec1()) ++ int(u16, @intFromEnum(tls.NamedGroup.x25519)) ++ array(u16, u8, key_share.x25519_kp.public_key), )); const server_name_extension = int(u16, @intFromEnum(tls.ExtensionType.server_name)) ++ int(u16, 2 + 1 + 2 + host_len) ++ // byte length of this extension payload int(u16, 1 + 2 + host_len) ++ // server_name_list byte count .{0x00} ++ // name_type int(u16, host_len); const server_name_extension_len = switch (options.host) { .no_verification => 0, .explicit => server_name_extension.len + host_len, }; const extensions_header = int(u16, @intCast(extensions_payload.len + server_name_extension_len)) ++ extensions_payload ++ server_name_extension; const client_hello = int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++ client_hello_rand ++ [1]u8{32} ++ legacy_session_id ++ cipher_suites ++ array(u8, tls.CompressionMethod, .{.null}) ++ extensions_header; const out_handshake = .{@intFromEnum(tls.HandshakeType.client_hello)} ++ int(u24, @intCast(client_hello.len - server_name_extension.len + server_name_extension_len)) ++ client_hello; const cleartext_header_buf = .{@intFromEnum(tls.ContentType.handshake)} ++ int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_0)) ++ int(u16, @intCast(out_handshake.len - server_name_extension.len + server_name_extension_len)) ++ out_handshake; const cleartext_header = switch (options.host) { .no_verification => cleartext_header_buf[0 .. cleartext_header_buf.len - server_name_extension.len], .explicit => &cleartext_header_buf, }; { var iovecs: [2][]const u8 = .{ cleartext_header, host }; try output.writeVecAll(iovecs[0..if (host.len == 0) 1 else 2]); try output.flush(); } var tls_version: tls.ProtocolVersion = undefined; // These are used for two purposes: // * Detect whether a certificate is the first one presented, in which case // we need to verify the host name. var cert_index: usize = 0; // * Flip back and forth between the two cleartext buffers in order to keep // the previous certificate in memory so that it can be verified by the // next one. var cert_buf_index: usize = 0; var write_seq: u64 = 0; var read_seq: u64 = 0; var prev_cert: Certificate.Parsed = undefined; const CipherState = enum { /// No cipher is in use cleartext, /// Handshake cipher is in use handshake, /// Application cipher is in use application, }; var pending_cipher_state: CipherState = .cleartext; var cipher_state = pending_cipher_state; const HandshakeState = enum { /// In this state we expect only a server hello message. hello, /// In this state we expect only an encrypted_extensions message. encrypted_extensions, /// In this state we expect certificate handshake messages. certificate, /// In this state we expect certificate or certificate_verify messages. /// certificate messages are ignored since the trust chain is already /// established. trust_chain_established, /// In this state, we expect only the server_hello_done handshake message. server_hello_done, /// In this state, we expect only the finished handshake message. finished, }; var handshake_state: HandshakeState = .hello; var handshake_cipher: tls.HandshakeCipher = undefined; var main_cert_pub_key: CertificatePublicKey = undefined; var tls12_negotiated_group: ?tls.NamedGroup = null; const now_sec = std.time.timestamp(); var cleartext_fragment_start: usize = 0; var cleartext_fragment_end: usize = 0; var cleartext_bufs: [2][tls.max_ciphertext_inner_record_len]u8 = undefined; fragment: while (true) { // Ensure the input buffer pointer is stable in this scope. input.rebase(tls.max_ciphertext_record_len) catch |err| switch (err) { error.EndOfStream => {}, // We have assurance the remainder of stream can be buffered. }; const record_header = input.peek(tls.record_header_len) catch |err| switch (err) { error.EndOfStream => return error.TlsConnectionTruncated, error.ReadFailed => return error.ReadFailed, }; const record_ct = input.takeEnumNonexhaustive(tls.ContentType, .big) catch unreachable; // already peeked input.toss(2); // legacy_version const record_len = input.takeInt(u16, .big) catch unreachable; // already peeked if (record_len > tls.max_ciphertext_len) return error.TlsRecordOverflow; const record_buffer = input.take(record_len) catch |err| switch (err) { error.EndOfStream => return error.TlsConnectionTruncated, error.ReadFailed => return error.ReadFailed, }; var record_decoder: tls.Decoder = .fromTheirSlice(record_buffer); var ctd, const ct = content: switch (cipher_state) { .cleartext => .{ record_decoder, record_ct }, .handshake => { assert(tls_version == .tls_1_3); if (record_ct != .application_data) return error.TlsUnexpectedMessage; try record_decoder.ensure(record_len); const cleartext_buf = &cleartext_bufs[cert_buf_index % 2]; switch (handshake_cipher) { inline else => |*p| { const pv = &p.version.tls_1_3; const P = @TypeOf(p.*).A; if (record_len < P.AEAD.tag_length) return error.TlsRecordOverflow; const ciphertext = record_decoder.slice(record_len - P.AEAD.tag_length); const cleartext_fragment_buf = cleartext_buf[cleartext_fragment_end..]; if (ciphertext.len > cleartext_fragment_buf.len) return error.TlsRecordOverflow; const cleartext = cleartext_fragment_buf[0..ciphertext.len]; const auth_tag = record_decoder.array(P.AEAD.tag_length).*; 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 ++ @as([8]u8, @bitCast(big(read_seq))); break :nonce @as(V, pv.server_handshake_iv) ^ operand; }; P.AEAD.decrypt(cleartext, ciphertext, auth_tag, record_header, nonce, pv.server_handshake_key) catch return error.TlsBadRecordMac; // TODO use scalar, non-slice version cleartext_fragment_end += mem.trimEnd(u8, cleartext, "\x00").len; }, } read_seq += 1; cleartext_fragment_end -= 1; const ct: tls.ContentType = @enumFromInt(cleartext_buf[cleartext_fragment_end]); if (ct != .handshake) return error.TlsUnexpectedMessage; break :content .{ tls.Decoder.fromTheirSlice(@constCast(cleartext_buf[cleartext_fragment_start..cleartext_fragment_end])), ct }; }, .application => { assert(tls_version == .tls_1_2); if (record_ct != .handshake) return error.TlsUnexpectedMessage; try record_decoder.ensure(record_len); const cleartext_buf = &cleartext_bufs[cert_buf_index % 2]; switch (handshake_cipher) { inline else => |*p| { const pv = &p.version.tls_1_2; const P = @TypeOf(p.*).A; if (record_len < P.record_iv_length + P.mac_length) return error.TlsRecordOverflow; const message_len: u16 = record_len - P.record_iv_length - P.mac_length; const cleartext_fragment_buf = cleartext_buf[cleartext_fragment_end..]; if (message_len > cleartext_fragment_buf.len) return error.TlsRecordOverflow; const cleartext = cleartext_fragment_buf[0..message_len]; const ad = mem.toBytes(big(read_seq)) ++ record_header[0 .. 1 + 2] ++ mem.toBytes(big(message_len)); const record_iv = record_decoder.array(P.record_iv_length).*; const masked_read_seq = 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.app_cipher.server_write_IV ++ record_iv) ^ operand; }; const ciphertext = record_decoder.slice(message_len); const auth_tag = record_decoder.array(P.mac_length); P.AEAD.decrypt(cleartext, ciphertext, auth_tag.*, ad, nonce, pv.app_cipher.server_write_key) catch return error.TlsBadRecordMac; cleartext_fragment_end += message_len; }, } read_seq += 1; break :content .{ tls.Decoder.fromTheirSlice(cleartext_buf[cleartext_fragment_start..cleartext_fragment_end]), record_ct }; }, }; switch (ct) { .alert => { ctd.ensure(2) catch continue :fragment; if (options.alert) |a| a.* = .{ .level = ctd.decode(tls.Alert.Level), .description = ctd.decode(tls.Alert.Description), }; return error.TlsAlert; }, .change_cipher_spec => { ctd.ensure(1) catch continue :fragment; if (ctd.decode(tls.ChangeCipherSpecType) != .change_cipher_spec) return error.TlsIllegalParameter; cipher_state = pending_cipher_state; }, .handshake => while (true) { ctd.ensure(4) catch continue :fragment; const handshake_type = ctd.decode(tls.HandshakeType); const handshake_len = ctd.decode(u24); var hsd = ctd.sub(handshake_len) catch continue :fragment; const wrapped_handshake = ctd.buf[ctd.idx - handshake_len - 4 .. ctd.idx]; switch (handshake_type) { .server_hello => { if (cipher_state != .cleartext) return error.TlsUnexpectedMessage; if (handshake_state != .hello) return error.TlsUnexpectedMessage; try hsd.ensure(2 + 32 + 1); const legacy_version = hsd.decode(u16); @memcpy(&server_hello_rand, hsd.array(32)); if (mem.eql(u8, &server_hello_rand, &tls.hello_retry_request_sequence)) { // This is a HelloRetryRequest message. This client implementation // does not expect to get one. return error.TlsUnexpectedMessage; } const legacy_session_id_echo_len = hsd.decode(u8); try hsd.ensure(legacy_session_id_echo_len + 2 + 1); const legacy_session_id_echo = hsd.slice(legacy_session_id_echo_len); const cipher_suite_tag = hsd.decode(tls.CipherSuite); hsd.skip(1); // legacy_compression_method var supported_version: ?u16 = null; if (!hsd.eof()) { try hsd.ensure(2); const extensions_size = hsd.decode(u16); var all_extd = try hsd.sub(extensions_size); while (!all_extd.eof()) { try all_extd.ensure(2 + 2); const et = all_extd.decode(tls.ExtensionType); const ext_size = all_extd.decode(u16); var extd = try all_extd.sub(ext_size); switch (et) { .supported_versions => { if (supported_version) |_| return error.TlsIllegalParameter; try extd.ensure(2); supported_version = extd.decode(u16); }, .key_share => { if (key_share.getSharedSecret()) |_| return error.TlsIllegalParameter; try extd.ensure(4); const named_group = extd.decode(tls.NamedGroup); const key_size = extd.decode(u16); try extd.ensure(key_size); try key_share.exchange(named_group, extd.slice(key_size)); }, else => {}, } } } tls_version = @enumFromInt(supported_version orelse legacy_version); switch (tls_version) { .tls_1_3 => if (!mem.eql(u8, legacy_session_id_echo, &legacy_session_id)) return error.TlsIllegalParameter, .tls_1_2 => if (mem.eql(u8, server_hello_rand[24..31], "DOWNGRD") and server_hello_rand[31] >> 1 == 0x00) return error.TlsIllegalParameter, else => return error.TlsIllegalParameter, } switch (cipher_suite_tag) { inline .AES_128_GCM_SHA256, .AES_256_GCM_SHA384, .CHACHA20_POLY1305_SHA256, .AEGIS_256_SHA512, .AEGIS_128L_SHA256, .ECDHE_RSA_WITH_AES_128_GCM_SHA256, .ECDHE_RSA_WITH_AES_256_GCM_SHA384, .ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, => |tag| { handshake_cipher = @unionInit(tls.HandshakeCipher, @tagName(tag.with()), .{ .transcript_hash = .init(.{}), .version = undefined, }); const p = &@field(handshake_cipher, @tagName(tag.with())); p.transcript_hash.update(cleartext_header[tls.record_header_len..]); // Client Hello part 1 p.transcript_hash.update(host); // Client Hello part 2 p.transcript_hash.update(wrapped_handshake); }, else => return error.TlsIllegalParameter, } switch (tls_version) { .tls_1_3 => { switch (cipher_suite_tag) { inline .AES_128_GCM_SHA256, .AES_256_GCM_SHA384, .CHACHA20_POLY1305_SHA256, .AEGIS_256_SHA512, .AEGIS_128L_SHA256, => |tag| { const sk = key_share.getSharedSecret() orelse return error.TlsIllegalParameter; const p = &@field(handshake_cipher, @tagName(tag.with())); const P = @TypeOf(p.*).A; const hello_hash = p.transcript_hash.peek(); const zeroes = [1]u8{0} ** P.Hash.digest_length; const early_secret = P.Hkdf.extract(&[1]u8{0}, &zeroes); const empty_hash = tls.emptyHash(P.Hash); p.version = .{ .tls_1_3 = undefined }; const pv = &p.version.tls_1_3; const hs_derived_secret = hkdfExpandLabel(P.Hkdf, early_secret, "derived", &empty_hash, P.Hash.digest_length); pv.handshake_secret = P.Hkdf.extract(&hs_derived_secret, sk); const ap_derived_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "derived", &empty_hash, P.Hash.digest_length); pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes); const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length); const server_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length); if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{ .client_random = &client_hello_rand, }, .{ .SERVER_HANDSHAKE_TRAFFIC_SECRET = &server_secret, .CLIENT_HANDSHAKE_TRAFFIC_SECRET = &client_secret, }); pv.client_finished_key = hkdfExpandLabel(P.Hkdf, client_secret, "finished", "", P.Hmac.key_length); pv.server_finished_key = hkdfExpandLabel(P.Hkdf, server_secret, "finished", "", P.Hmac.key_length); pv.client_handshake_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length); pv.server_handshake_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length); pv.client_handshake_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length); pv.server_handshake_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length); }, else => return error.TlsIllegalParameter, } pending_cipher_state = .handshake; handshake_state = .encrypted_extensions; }, .tls_1_2 => switch (cipher_suite_tag) { .ECDHE_RSA_WITH_AES_128_GCM_SHA256, .ECDHE_RSA_WITH_AES_256_GCM_SHA384, .ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, => handshake_state = .certificate, else => return error.TlsIllegalParameter, }, else => return error.TlsIllegalParameter, } }, .encrypted_extensions => { if (tls_version != .tls_1_3) return error.TlsUnexpectedMessage; if (cipher_state != .handshake) return error.TlsUnexpectedMessage; if (handshake_state != .encrypted_extensions) return error.TlsUnexpectedMessage; switch (handshake_cipher) { inline else => |*p| p.transcript_hash.update(wrapped_handshake), } try hsd.ensure(2); const total_ext_size = hsd.decode(u16); var all_extd = try hsd.sub(total_ext_size); while (!all_extd.eof()) { try all_extd.ensure(4); const et = all_extd.decode(tls.ExtensionType); const ext_size = all_extd.decode(u16); const extd = try all_extd.sub(ext_size); _ = extd; switch (et) { .server_name => {}, else => {}, } } handshake_state = .certificate; }, .certificate => cert: { if (cipher_state == .application) return error.TlsUnexpectedMessage; switch (handshake_state) { .certificate => {}, .trust_chain_established => break :cert, else => return error.TlsUnexpectedMessage, } switch (handshake_cipher) { inline else => |*p| p.transcript_hash.update(wrapped_handshake), } switch (tls_version) { .tls_1_3 => { try hsd.ensure(1 + 3); const cert_req_ctx_len = hsd.decode(u8); if (cert_req_ctx_len != 0) return error.TlsIllegalParameter; }, .tls_1_2 => try hsd.ensure(3), else => unreachable, } const certs_size = hsd.decode(u24); var certs_decoder = try hsd.sub(certs_size); while (!certs_decoder.eof()) { try certs_decoder.ensure(3); const cert_size = certs_decoder.decode(u24); const certd = try certs_decoder.sub(cert_size); if (tls_version == .tls_1_3) { try certs_decoder.ensure(2); const total_ext_size = certs_decoder.decode(u16); const all_extd = try certs_decoder.sub(total_ext_size); _ = all_extd; } const subject_cert: Certificate = .{ .buffer = certd.buf, .index = @intCast(certd.idx), }; const subject = try subject_cert.parse(); if (cert_index == 0) { // Verify the host on the first certificate. switch (options.host) { .no_verification => {}, .explicit => try subject.verifyHostName(host), } // Keep track of the public key for the // certificate_verify message later. try main_cert_pub_key.init(subject.pub_key_algo, subject.pubKey()); } else { try prev_cert.verify(subject, now_sec); } switch (options.ca) { .no_verification => { handshake_state = .trust_chain_established; break :cert; }, .self_signed => { try subject.verify(subject, now_sec); handshake_state = .trust_chain_established; break :cert; }, .bundle => |ca_bundle| if (ca_bundle.verify(subject, now_sec)) |_| { handshake_state = .trust_chain_established; break :cert; } else |err| switch (err) { error.CertificateIssuerNotFound => {}, else => |e| return e, }, } prev_cert = subject; cert_index += 1; } cert_buf_index += 1; }, .server_key_exchange => { if (tls_version != .tls_1_2) return error.TlsUnexpectedMessage; if (cipher_state != .cleartext) return error.TlsUnexpectedMessage; switch (handshake_state) { .trust_chain_established => {}, .certificate => return error.TlsCertificateNotVerified, else => return error.TlsUnexpectedMessage, } switch (handshake_cipher) { inline else => |*p| p.transcript_hash.update(wrapped_handshake), } try hsd.ensure(1 + 2 + 1); const curve_type = hsd.decode(u8); if (curve_type != 0x03) return error.TlsIllegalParameter; // named_curve const named_group = hsd.decode(tls.NamedGroup); tls12_negotiated_group = named_group; const key_size = hsd.decode(u8); try hsd.ensure(key_size); const server_pub_key = hsd.slice(key_size); try main_cert_pub_key.verifySignature(&hsd, &.{ &client_hello_rand, &server_hello_rand, hsd.buf[0..hsd.idx] }); try key_share.exchange(named_group, server_pub_key); handshake_state = .server_hello_done; }, .server_hello_done => { if (tls_version != .tls_1_2) return error.TlsUnexpectedMessage; if (cipher_state != .cleartext) return error.TlsUnexpectedMessage; if (handshake_state != .server_hello_done) return error.TlsUnexpectedMessage; const public_key_bytes: []const u8 = switch (tls12_negotiated_group orelse .secp256r1) { .secp256r1 => &key_share.secp256r1_kp.public_key.toUncompressedSec1(), .secp384r1 => &key_share.secp384r1_kp.public_key.toUncompressedSec1(), .x25519 => &key_share.x25519_kp.public_key, else => return error.TlsIllegalParameter, }; const client_key_exchange_prefix = .{@intFromEnum(tls.ContentType.handshake)} ++ int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++ int(u16, @intCast(public_key_bytes.len + 5)) ++ // record length .{@intFromEnum(tls.HandshakeType.client_key_exchange)} ++ int(u24, @intCast(public_key_bytes.len + 1)) ++ // handshake message length .{@as(u8, @intCast(public_key_bytes.len))}; // public key length const client_change_cipher_spec_msg = .{@intFromEnum(tls.ContentType.change_cipher_spec)} ++ int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++ array(u16, tls.ChangeCipherSpecType, .{.change_cipher_spec}); const pre_master_secret = key_share.getSharedSecret().?; switch (handshake_cipher) { inline else => |*p| { const P = @TypeOf(p.*).A; p.transcript_hash.update(wrapped_handshake); p.transcript_hash.update(client_key_exchange_prefix[tls.record_header_len..]); p.transcript_hash.update(public_key_bytes); const master_secret = hmacExpandLabel(P.Hmac, pre_master_secret, &.{ "master secret", &client_hello_rand, &server_hello_rand, }, 48); if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{ .client_random = &client_hello_rand, }, .{ .CLIENT_RANDOM = &master_secret, }); const key_block = hmacExpandLabel( P.Hmac, &master_secret, &.{ "key expansion", &server_hello_rand, &client_hello_rand }, @sizeOf(P.Tls_1_2), ); const client_verify_cleartext = .{@intFromEnum(tls.HandshakeType.finished)} ++ array(u24, u8, hmacExpandLabel( P.Hmac, &master_secret, &.{ "client finished", &p.transcript_hash.peek() }, P.verify_data_length, )); p.transcript_hash.update(&client_verify_cleartext); p.version = .{ .tls_1_2 = .{ .expected_server_verify_data = hmacExpandLabel( P.Hmac, &master_secret, &.{ "server finished", &p.transcript_hash.finalResult() }, P.verify_data_length, ), .app_cipher = mem.bytesToValue(P.Tls_1_2, &key_block), } }; const pv = &p.version.tls_1_2; 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(write_seq))); break :nonce @as(V, pv.app_cipher.client_write_IV ++ pv.app_cipher.client_salt) ^ operand; }; var client_verify_msg = .{@intFromEnum(tls.ContentType.handshake)} ++ int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++ array(u16, u8, nonce[P.fixed_iv_length..].* ++ @as([client_verify_cleartext.len + P.mac_length]u8, undefined)); P.AEAD.encrypt( client_verify_msg[client_verify_msg.len - P.mac_length - client_verify_cleartext.len ..][0..client_verify_cleartext.len], client_verify_msg[client_verify_msg.len - P.mac_length ..][0..P.mac_length], &client_verify_cleartext, mem.toBytes(big(write_seq)) ++ client_verify_msg[0 .. 1 + 2] ++ int(u16, client_verify_cleartext.len), nonce, pv.app_cipher.client_write_key, ); var all_msgs_vec: [4][]const u8 = .{ &client_key_exchange_prefix, public_key_bytes, &client_change_cipher_spec_msg, &client_verify_msg, }; try output.writeVecAll(&all_msgs_vec); try output.flush(); }, } write_seq += 1; pending_cipher_state = .application; handshake_state = .finished; }, .certificate_verify => { if (tls_version != .tls_1_3) return error.TlsUnexpectedMessage; if (cipher_state != .handshake) return error.TlsUnexpectedMessage; switch (handshake_state) { .trust_chain_established => {}, .certificate => return error.TlsCertificateNotVerified, else => return error.TlsUnexpectedMessage, } switch (handshake_cipher) { inline else => |*p| { try main_cert_pub_key.verifySignature(&hsd, &.{ " " ** 64 ++ "TLS 1.3, server CertificateVerify\x00", &p.transcript_hash.peek(), }); p.transcript_hash.update(wrapped_handshake); }, } handshake_state = .finished; }, .finished => { if (cipher_state == .cleartext) return error.TlsUnexpectedMessage; if (handshake_state != .finished) return error.TlsUnexpectedMessage; // This message is to trick buggy proxies into behaving correctly. const client_change_cipher_spec_msg = .{@intFromEnum(tls.ContentType.change_cipher_spec)} ++ int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++ array(u16, tls.ChangeCipherSpecType, .{.change_cipher_spec}); const app_cipher = app_cipher: switch (handshake_cipher) { inline else => |*p, tag| switch (tls_version) { .tls_1_3 => { const pv = &p.version.tls_1_3; const P = @TypeOf(p.*).A; try hsd.ensure(P.Hmac.mac_length); const finished_digest = p.transcript_hash.peek(); p.transcript_hash.update(wrapped_handshake); const expected_server_verify_data = tls.hmac(P.Hmac, &finished_digest, pv.server_finished_key); if (!std.crypto.timing_safe.eql([P.Hmac.mac_length]u8, expected_server_verify_data, hsd.array(P.Hmac.mac_length).*)) return error.TlsDecryptError; const handshake_hash = p.transcript_hash.finalResult(); const verify_data = tls.hmac(P.Hmac, &handshake_hash, pv.client_finished_key); const out_cleartext = .{@intFromEnum(tls.HandshakeType.finished)} ++ array(u24, u8, verify_data) ++ .{@intFromEnum(tls.ContentType.handshake)}; const wrapped_len = out_cleartext.len + P.AEAD.tag_length; var finished_msg = .{@intFromEnum(tls.ContentType.application_data)} ++ int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++ array(u16, u8, @as([wrapped_len]u8, undefined)); const ad = finished_msg[0..tls.record_header_len]; const ciphertext = finished_msg[tls.record_header_len..][0..out_cleartext.len]; const auth_tag = finished_msg[finished_msg.len - P.AEAD.tag_length ..]; const nonce = pv.client_handshake_iv; P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, pv.client_handshake_key); var all_msgs_vec: [2][]const u8 = .{ &client_change_cipher_spec_msg, &finished_msg, }; try output.writeVecAll(&all_msgs_vec); try output.flush(); const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{ .counter = key_seq, .client_random = &client_hello_rand, }, .{ .SERVER_TRAFFIC_SECRET = &server_secret, .CLIENT_TRAFFIC_SECRET = &client_secret, }); key_seq += 1; break :app_cipher @unionInit(tls.ApplicationCipher, @tagName(tag), .{ .tls_1_3 = .{ .client_secret = client_secret, .server_secret = server_secret, .client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length), .server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length), .client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length), .server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length), } }); }, .tls_1_2 => { const pv = &p.version.tls_1_2; const P = @TypeOf(p.*).A; try hsd.ensure(P.verify_data_length); if (!std.crypto.timing_safe.eql([P.verify_data_length]u8, pv.expected_server_verify_data, hsd.array(P.verify_data_length).*)) return error.TlsDecryptError; break :app_cipher @unionInit(tls.ApplicationCipher, @tagName(tag), .{ .tls_1_2 = pv.app_cipher }); }, else => unreachable, }, }; if (options.ssl_key_log) |ssl_key_log| ssl_key_log.* = .{ .client_key_seq = key_seq, .server_key_seq = key_seq, .client_random = client_hello_rand, .writer = ssl_key_log.writer, }; return .{ .input = input, .reader = .{ .buffer = options.read_buffer, .vtable = &.{ .stream = stream, .readVec = readVec, }, .seek = 0, .end = 0, }, .output = output, .writer = .{ .buffer = options.write_buffer, .vtable = &.{ .drain = drain, .flush = flush, }, }, .tls_version = tls_version, .read_seq = switch (tls_version) { .tls_1_3 => 0, .tls_1_2 => read_seq, else => unreachable, }, .write_seq = switch (tls_version) { .tls_1_3 => 0, .tls_1_2 => write_seq, else => unreachable, }, .received_close_notify = false, .allow_truncation_attacks = options.allow_truncation_attacks, .application_cipher = app_cipher, .ssl_key_log = options.ssl_key_log, }; }, else => return error.TlsUnexpectedMessage, } if (ctd.eof()) break; cleartext_fragment_start = ctd.idx; }, else => return error.TlsUnexpectedMessage, } cleartext_fragment_start = 0; cleartext_fragment_end = 0; } }