| ... | ... | @@ -344,45 +344,73 @@ pub const Parsed = struct { |
| 344 | 344 | // component or component fragment. E.g., *.a.com matches foo.a.com but |
| 345 | 345 | // not bar.foo.a.com. f*.com matches foo.com but not bar.com. |
| 346 | 346 | fn checkHostName(host_name: []const u8, dns_name: []const u8) bool { |
| 347 | // Empty strings should not match |
| 348 | if (host_name.len == 0 or dns_name.len == 0) return false; |
| 349 | |
| 350 | // RFC 6125 Section 6.4.1: Exact match (case-insensitive) |
| 347 | 351 | if (std.ascii.eqlIgnoreCase(dns_name, host_name)) { |
| 348 | 352 | return true; // exact match |
| 349 | 353 | } |
| 350 | 354 | |
| 351 | | var it_host = std.mem.splitScalar(u8, host_name, '.'); |
| 352 | | var it_dns = std.mem.splitScalar(u8, dns_name, '.'); |
| 355 | // RFC 6125 Section 6.4.3: Wildcard certificates |
| 356 | // Wildcard must be leftmost label and in the form "*.rest.of.domain" |
| 357 | if (dns_name.len >= 3 and mem.startsWith(u8, dns_name, "*.")) { |
| 358 | const wildcard_suffix = dns_name[2..]; |
| 353 | 359 | |
| 354 | | const len_match = while (true) { |
| 355 | | const host = it_host.next(); |
| 356 | | const dns = it_dns.next(); |
| 360 | // No additional wildcards allowed in the suffix |
| 361 | if (mem.indexOf(u8, wildcard_suffix, "*") != null) return false; |
| 357 | 362 | |
| 358 | | if (host == null or dns == null) { |
| 359 | | break host == null and dns == null; |
| 360 | | } |
| 363 | // Find the first dot in hostname to split first label from rest |
| 364 | const dot_pos = mem.indexOf(u8, host_name, ".") orelse return false; |
| 361 | 365 | |
| 362 | | // If not a wildcard and they dont |
| 363 | | // match then there is no match. |
| 364 | | if (mem.eql(u8, dns.?, "*") == false and std.ascii.eqlIgnoreCase(dns.?, host.?) == false) { |
| 365 | | return false; |
| 366 | | } |
| 367 | | }; |
| 366 | // Wildcard matches exactly one label, so compare the rest |
| 367 | const host_suffix = host_name[dot_pos + 1 ..]; |
| 368 | 368 | |
| 369 | | // If the components are not the same |
| 370 | | // length then there is no match. |
| 371 | | return len_match; |
| 369 | // Match suffixes (case-insensitive per RFC 6125) |
| 370 | return std.ascii.eqlIgnoreCase(wildcard_suffix, host_suffix); |
| 371 | } |
| 372 | |
| 373 | return false; |
| 372 | 374 | } |
| 373 | 375 | }; |
| 374 | 376 | |
| 375 | | test "Parsed.checkHostName" { |
| 377 | test "Parsed.checkHostName RFC 6125 compliance" { |
| 376 | 378 | const expectEqual = std.testing.expectEqual; |
| 377 | 379 | |
| 380 | // Exact match tests |
| 378 | 381 | try expectEqual(true, Parsed.checkHostName("ziglang.org", "ziglang.org")); |
| 382 | try expectEqual(true, Parsed.checkHostName("ziglang.org", "Ziglang.org")); // case insensitive |
| 383 | try expectEqual(true, Parsed.checkHostName("ZIGLANG.ORG", "ziglang.org")); // case insensitive |
| 384 | |
| 385 | // Valid wildcard matches |
| 379 | 386 | try expectEqual(true, Parsed.checkHostName("bar.ziglang.org", "*.ziglang.org")); |
| 387 | try expectEqual(true, Parsed.checkHostName("BAR.ziglang.org", "*.Ziglang.ORG")); // case insensitive |
| 388 | |
| 389 | // RFC 6125: Wildcard matches exactly one label |
| 380 | 390 | try expectEqual(false, Parsed.checkHostName("foo.bar.ziglang.org", "*.ziglang.org")); |
| 391 | try expectEqual(false, Parsed.checkHostName("ziglang.org", "*.ziglang.org")); // no empty match |
| 392 | |
| 393 | // RFC 6125: No partial wildcards allowed |
| 381 | 394 | try expectEqual(false, Parsed.checkHostName("ziglang.org", "zig*.org")); |
| 382 | | try expectEqual(false, Parsed.checkHostName("lang.org", "zig*.org")); |
| 383 | | // host name check should be case insensitive |
| 384 | | try expectEqual(true, Parsed.checkHostName("ziglang.org", "Ziglang.org")); |
| 385 | | try expectEqual(true, Parsed.checkHostName("bar.ziglang.org", "*.Ziglang.ORG")); |
| 395 | try expectEqual(false, Parsed.checkHostName("ziglang.org", "*lang.org")); |
| 396 | try expectEqual(false, Parsed.checkHostName("ziglang.org", "zi*ng.org")); |
| 397 | |
| 398 | // RFC 6125: No multiple wildcards |
| 399 | try expectEqual(false, Parsed.checkHostName("foo.bar.org", "*.*.org")); |
| 400 | |
| 401 | // RFC 6125: Wildcard must be in leftmost label |
| 402 | try expectEqual(false, Parsed.checkHostName("foo.bar.org", "foo.*.org")); |
| 403 | |
| 404 | // Single label hostnames should not match wildcards |
| 405 | try expectEqual(false, Parsed.checkHostName("localhost", "*.local")); |
| 406 | try expectEqual(false, Parsed.checkHostName("localhost", "*.localhost")); |
| 407 | |
| 408 | // Edge cases |
| 409 | try expectEqual(false, Parsed.checkHostName("", "")); |
| 410 | try expectEqual(false, Parsed.checkHostName("example.com", "")); |
| 411 | try expectEqual(false, Parsed.checkHostName("", "*.example.com")); |
| 412 | try expectEqual(false, Parsed.checkHostName("example.com", "*")); |
| 413 | try expectEqual(false, Parsed.checkHostName("example.com", "*.")); |
| 386 | 414 | } |
| 387 | 415 | |
| 388 | 416 | pub const ParseError = der.Element.ParseError || ParseVersionError || ParseTimeError || ParseEnumError || ParseBitStringError; |