authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-07-08 23:37:44+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-09 16:35:41-04:00
logc1e7eb738934399737b3a8452ad9b68bb26805d3
tree8f1249e0acf98492730adbfe4e5594b84930e9af
parent2511830442fd96d04f578f2c4251b1994f08c994

crypto.Certificate: case insensitive host name check

This makes comparing host name with dns name from certificate case insensitive. I found a few domains (from the [cloudflare](https://radar.cloudflare.com/domains) list of top domains) for which tls.Client fails to connect. Error is: ```zig error: TlsInitializationFailed Code/zig/lib/std/crypto/Certificate.zig:336:9: 0x1177b1f in verifyHostName (http_get_std) return error.CertificateHostMismatch; Code/zig/lib/std/crypto/tls23/handshake_client.zig:461:25: 0x11752bd in parseServerCertificate (http_get_std) try subject.verifyHostName(opt.host); ``` In its certificate this domains have host names which are not strictly lower case. This is what checkHostName is comparing: |host_name | dns_name | |------------------------------------------------| |ey.com | EY.COM | |truist.com | Truist.com | |wscampanhas.bradesco | WSCAMPANHAS.BRADESCO | |dell.com | Dell.com | From [RFC2818](https://datatracker.ietf.org/doc/html/rfc2818#section-2.4): > Matching is performed using the matching rules specified by [RFC2459]. From [RFC2459](https://datatracker.ietf.org/doc/html/rfc2459#section-4.2.1.7): > When comparing URIs, conforming implementations > MUST compare the scheme and host without regard to case, but assume > the remainder of the scheme-specific-part is case sensitive. Testing with: ``` 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(); } } ``` `$ zig run example/main.zig -- truist.com `

1 files changed, 5 insertions(+), 2 deletions(-)

lib/std/crypto/Certificate.zig+5-2
...@@ -345,7 +345,7 @@ pub const Parsed = struct {...@@ -345,7 +345,7 @@ pub const Parsed = struct {
345 // component or component fragment. E.g., *.a.com matches foo.a.com but345 // component or component fragment. E.g., *.a.com matches foo.a.com but
346 // not bar.foo.a.com. f*.com matches foo.com but not bar.com.346 // not bar.foo.a.com. f*.com matches foo.com but not bar.com.
347 fn checkHostName(host_name: []const u8, dns_name: []const u8) bool {347 fn checkHostName(host_name: []const u8, dns_name: []const u8) bool {
348 if (mem.eql(u8, dns_name, host_name)) {348 if (std.ascii.eqlIgnoreCase(dns_name, host_name)) {
349 return true; // exact match349 return true; // exact match
350 }350 }
351351
...@@ -362,7 +362,7 @@ pub const Parsed = struct {...@@ -362,7 +362,7 @@ pub const Parsed = struct {
362362
363 // If not a wildcard and they dont363 // If not a wildcard and they dont
364 // match then there is no match.364 // match then there is no match.
365 if (mem.eql(u8, dns.?, "*") == false and mem.eql(u8, dns.?, host.?) == false) {365 if (mem.eql(u8, dns.?, "*") == false and std.ascii.eqlIgnoreCase(dns.?, host.?) == false) {
366 return false;366 return false;
367 }367 }
368 };368 };
...@@ -381,6 +381,9 @@ test "Parsed.checkHostName" {...@@ -381,6 +381,9 @@ test "Parsed.checkHostName" {
381 try expectEqual(false, Parsed.checkHostName("foo.bar.ziglang.org", "*.ziglang.org"));381 try expectEqual(false, Parsed.checkHostName("foo.bar.ziglang.org", "*.ziglang.org"));
382 try expectEqual(false, Parsed.checkHostName("ziglang.org", "zig*.org"));382 try expectEqual(false, Parsed.checkHostName("ziglang.org", "zig*.org"));
383 try expectEqual(false, Parsed.checkHostName("lang.org", "zig*.org"));383 try expectEqual(false, Parsed.checkHostName("lang.org", "zig*.org"));
384 // host name check should be case insensitive
385 try expectEqual(true, Parsed.checkHostName("ziglang.org", "Ziglang.org"));
386 try expectEqual(true, Parsed.checkHostName("bar.ziglang.org", "*.Ziglang.ORG"));
384}387}
385388
386pub const ParseError = der.Element.ParseElementError || ParseVersionError || ParseTimeError || ParseEnumError || ParseBitStringError;389pub const ParseError = der.Element.ParseElementError || ParseVersionError || ParseTimeError || ParseEnumError || ParseBitStringError;