From aa73bb6bc969ef053a8fab265da8bf28e024b2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Ani=C4=87?= Date: Sun, 21 Jul 2024 10:19:36 +0200 Subject: [PATCH] tls.Client: implement record padding (#20558) On decryption tls client should remove zero byte padding after the content type field. This padding is rarely used, the only site (from the list of top domains) that I found using it is `tutanota.com`. From [RFC](https://datatracker.ietf.org/doc/html/rfc8446#section-5.4): > All encrypted TLS records can be padded. > Padding is a string of zero-valued bytes appended to the ContentType field before encryption. > the receiving implementation scans the field from the end toward the beginning until it finds a non-zero octet. This non-zero octet is the content type of the message. Currently we can't connect to that site: ``` $ zig run main.zig -- tutanota.com error: TlsInitializationFailed /usr/local/zig/zig-linux-x86_64-0.14.0-dev.208+854e86c56/lib/std/crypto/tls/Client.zig:476:45: 0x121fbed in init__anon_10331 (http_get_std) if (inner_ct != .handshake) return error.TlsUnexpectedMessage; ^ /usr/local/zig/zig-linux-x86_64-0.14.0-dev.208+854e86c56/lib/std/http/Client.zig:1357:99: 0x1161f0b in connectTcp (http_get_std) conn.data.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed; ^ /usr/local/zig/zig-linux-x86_64-0.14.0-dev.208+854e86c56/lib/std/http/Client.zig:1492:14: 0x11271e1 in connect (http_get_std) } orelse return client.connectTcp(host, port, protocol); ^ /usr/local/zig/zig-linux-x86_64-0.14.0-dev.208+854e86c56/lib/std/http/Client.zig:1640:9: 0x111a24e in open (http_get_std) try client.connect(valid_uri.host.?.raw, uriPort(valid_uri, protocol), protocol); ^ /home/ianic/Code/tls.zig/example/http_get_std.zig:28:19: 0x1118f8c in main (http_get_std) var req = try client.open(.GET, uri, .{ .server_header_buffer = &server_header_buffer }); ^ ``` using this example: ```zig const std = @import("std"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); if (args.len > 1) { const domain = args[1]; var client: std.http.Client = .{ .allocator = allocator }; defer client.deinit(); // Add https:// prefix if needed const url = brk: { const scheme = "https://"; if (domain.len >= scheme.len and std.mem.eql(u8, domain[0..scheme.len], scheme)) break :brk domain; var url_buf: [128]u8 = undefined; break :brk try std.fmt.bufPrint(&url_buf, "https://{s}", .{domain}); }; const uri = try std.Uri.parse(url); var server_header_buffer: [16 * 1024]u8 = undefined; var req = try client.open(.GET, uri, .{ .server_header_buffer = &server_header_buffer }); defer req.deinit(); try req.send(); try req.wait(); } } ``` --- lib/std/crypto/tls/Client.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index b68740bf6b..452ef5d0c5 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -468,7 +468,7 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In read_seq += 1; P.AEAD.decrypt(cleartext, ciphertext, auth_tag, record_header, nonce, p.server_handshake_key) catch return error.TlsBadRecordMac; - break :c cleartext; + break :c @constCast(mem.trimRight(u8, cleartext, "\x00")); }, }; @@ -1146,7 +1146,7 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove const cleartext = cleartext_buf[0..ciphertext.len]; P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, p.server_key) catch return error.TlsBadRecordMac; - break :c cleartext; + break :c mem.trimRight(u8, cleartext, "\x00"); }, };