Commit c1e7eb7389

Igor Anić <igor.anic@gmail.com>
2024-07-08 23:37:44
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 parent 2511830
Changed files (1)
lib
std
lib/std/crypto/Certificate.zig
@@ -345,7 +345,7 @@ pub const Parsed = struct {
     // component or component fragment. E.g., *.a.com matches foo.a.com but
     // not bar.foo.a.com. f*.com matches foo.com but not bar.com.
     fn checkHostName(host_name: []const u8, dns_name: []const u8) bool {
-        if (mem.eql(u8, dns_name, host_name)) {
+        if (std.ascii.eqlIgnoreCase(dns_name, host_name)) {
             return true; // exact match
         }
 
@@ -362,7 +362,7 @@ pub const Parsed = struct {
 
             // If not a wildcard and they dont
             // match then there is no match.
-            if (mem.eql(u8, dns.?, "*") == false and mem.eql(u8, dns.?, host.?) == false) {
+            if (mem.eql(u8, dns.?, "*") == false and std.ascii.eqlIgnoreCase(dns.?, host.?) == false) {
                 return false;
             }
         };
@@ -381,6 +381,9 @@ test "Parsed.checkHostName" {
     try expectEqual(false, Parsed.checkHostName("foo.bar.ziglang.org", "*.ziglang.org"));
     try expectEqual(false, Parsed.checkHostName("ziglang.org", "zig*.org"));
     try expectEqual(false, Parsed.checkHostName("lang.org", "zig*.org"));
+    // host name check should be case insensitive
+    try expectEqual(true, Parsed.checkHostName("ziglang.org", "Ziglang.org"));
+    try expectEqual(true, Parsed.checkHostName("bar.ziglang.org", "*.Ziglang.ORG"));
 }
 
 pub const ParseError = der.Element.ParseElementError || ParseVersionError || ParseTimeError || ParseEnumError || ParseBitStringError;