| ... | @@ -344,45 +344,73 @@ pub const Parsed = struct { | ... | @@ -344,45 +344,73 @@ pub const Parsed = struct { |
| 344 | // component or component fragment. E.g., *.a.com matches foo.a.com but | 344 | // component or component fragment. E.g., *.a.com matches foo.a.com but |
| 345 | // not bar.foo.a.com. f*.com matches foo.com but not bar.com. | 345 | // not bar.foo.a.com. f*.com matches foo.com but not bar.com. |
| 346 | fn checkHostName(host_name: []const u8, dns_name: []const u8) bool { | 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 | if (std.ascii.eqlIgnoreCase(dns_name, host_name)) { | 351 | if (std.ascii.eqlIgnoreCase(dns_name, host_name)) { |
| 348 | return true; // exact match | 352 | return true; // exact match |
| 349 | } | 353 | } |
| 350 | | 354 | |
| 351 | var it_host = std.mem.splitScalar(u8, host_name, '.'); | 355 | // RFC 6125 Section 6.4.3: Wildcard certificates |
| 352 | var it_dns = std.mem.splitScalar(u8, dns_name, '.'); | 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) { | 360 | // No additional wildcards allowed in the suffix |
| 355 | const host = it_host.next(); | 361 | if (mem.indexOf(u8, wildcard_suffix, "*") != null) return false; |
| 356 | const dns = it_dns.next(); | | |
| 357 | | 362 | |
| 358 | if (host == null or dns == null) { | 363 | // Find the first dot in hostname to split first label from rest |
| 359 | break host == null and dns == null; | 364 | const dot_pos = mem.indexOf(u8, host_name, ".") orelse return false; |
| 360 | } | | |
| 361 | | 365 | |
| 362 | // If not a wildcard and they dont | 366 | // Wildcard matches exactly one label, so compare the rest |
| 363 | // match then there is no match. | 367 | const host_suffix = host_name[dot_pos + 1 ..]; |
| 364 | if (mem.eql(u8, dns.?, "*") == false and std.ascii.eqlIgnoreCase(dns.?, host.?) == false) { | | |
| 365 | return false; | | |
| 366 | } | | |
| 367 | }; | | |
| 368 | | 368 | |
| 369 | // If the components are not the same | 369 | // Match suffixes (case-insensitive per RFC 6125) |
| 370 | // length then there is no match. | 370 | return std.ascii.eqlIgnoreCase(wildcard_suffix, host_suffix); |
| 371 | return len_match; | 371 | } |
| | 372 | |
| | 373 | return false; |
| 372 | } | 374 | } |
| 373 | }; | 375 | }; |
| 374 | | 376 | |
| 375 | test "Parsed.checkHostName" { | 377 | test "Parsed.checkHostName RFC 6125 compliance" { |
| 376 | const expectEqual = std.testing.expectEqual; | 378 | const expectEqual = std.testing.expectEqual; |
| 377 | | 379 | |
| | 380 | // Exact match tests |
| 378 | try expectEqual(true, Parsed.checkHostName("ziglang.org", "ziglang.org")); | 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 | try expectEqual(true, Parsed.checkHostName("bar.ziglang.org", "*.ziglang.org")); | 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 | try expectEqual(false, Parsed.checkHostName("foo.bar.ziglang.org", "*.ziglang.org")); | 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 | try expectEqual(false, Parsed.checkHostName("ziglang.org", "zig*.org")); | 394 | try expectEqual(false, Parsed.checkHostName("ziglang.org", "zig*.org")); |
| 382 | try expectEqual(false, Parsed.checkHostName("lang.org", "zig*.org")); | 395 | try expectEqual(false, Parsed.checkHostName("ziglang.org", "*lang.org")); |
| 383 | // host name check should be case insensitive | 396 | try expectEqual(false, Parsed.checkHostName("ziglang.org", "zi*ng.org")); |
| 384 | try expectEqual(true, Parsed.checkHostName("ziglang.org", "Ziglang.org")); | 397 | |
| 385 | try expectEqual(true, Parsed.checkHostName("bar.ziglang.org", "*.Ziglang.ORG")); | 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 | pub const ParseError = der.Element.ParseError || ParseVersionError || ParseTimeError || ParseEnumError || ParseBitStringError; | 416 | pub const ParseError = der.Element.ParseError || ParseVersionError || ParseTimeError || ParseEnumError || ParseBitStringError; |