Skip to content

Commit 6c98c8d

Browse files
authored
Wildcard certs should only validate one level of sub domain
1 parent 989b0e6 commit 6c98c8d

File tree

1 file changed

+36
-12
lines changed

1 file changed

+36
-12
lines changed

lib/std/crypto/Certificate.zig

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -316,29 +316,53 @@ pub const Parsed = struct {
316316
return error.CertificateHostMismatch;
317317
}
318318

319+
// Check hostname according to RFC2818 specification:
320+
//
321+
// If more than one identity of a given type is present in
322+
// the certificate (e.g., more than one DNSName name, a match in any one
323+
// of the set is considered acceptable.) Names may contain the wildcard
324+
// character * which is considered to match any single domain name
325+
// component or component fragment. E.g., *.a.com matches foo.a.com but
326+
// not bar.foo.a.com. f*.com matches foo.com but not bar.com.
319327
fn checkHostName(host_name: []const u8, dns_name: []const u8) bool {
320328
if (mem.eql(u8, dns_name, host_name)) {
321329
return true; // exact match
322330
}
323331

324-
if (mem.startsWith(u8, dns_name, "*.")) {
325-
// wildcard certificate, matches any subdomain
326-
// TODO: I think wildcards are not supposed to match any prefix but
327-
// only match exactly one subdomain.
328-
if (mem.endsWith(u8, host_name, dns_name[1..])) {
329-
// The host_name has a subdomain, but the important part matches.
330-
return true;
332+
var it_host = std.mem.split(u8, host_name, ".");
333+
var it_dns = std.mem.split(u8, dns_name, ".");
334+
335+
const len_match = while (true) {
336+
const host = it_host.next();
337+
const dns = it_dns.next();
338+
339+
if (host == null or dns == null) {
340+
break host == null and dns == null;
331341
}
332-
if (mem.eql(u8, dns_name[2..], host_name)) {
333-
// The host_name has no subdomain and matches exactly.
334-
return true;
342+
343+
// If not a wildcard and they dont
344+
// match then there is no match.
345+
if (mem.eql(u8, dns.?, "*") == false and mem.eql(u8, dns.?, host.?) == false) {
346+
return false;
335347
}
336-
}
348+
};
337349

338-
return false;
350+
// If the components are not the same
351+
// length then there is no match.
352+
return len_match;
339353
}
340354
};
341355

356+
test "Parsed.checkHostName" {
357+
const expectEqual = std.testing.expectEqual;
358+
359+
try expectEqual(true, Parsed.checkHostName("ziglang.org", "ziglang.org"));
360+
try expectEqual(true, Parsed.checkHostName("bar.ziglang.org", "*.ziglang.org"));
361+
try expectEqual(false, Parsed.checkHostName("foo.bar.ziglang.org", "*.ziglang.org"));
362+
try expectEqual(false, Parsed.checkHostName("ziglang.org", "zig*.org"));
363+
try expectEqual(false, Parsed.checkHostName("lang.org", "zig*.org"));
364+
}
365+
342366
pub fn parse(cert: Certificate) !Parsed {
343367
const cert_bytes = cert.buffer;
344368
const certificate = try der.Element.parse(cert_bytes, cert.index);

0 commit comments

Comments
 (0)