authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-04-20 12:13:52+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-04-20 22:37:42+02:00
logbafad9644cf1d5218bbadf127ddc7d63de596740
treeb8036e8c6388329e6aedb37bda7160e15449730c
parent97943b9a62fb053dd0d74ad105ef4da2fd1eff19
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

std.crypto.Certificate: fix UTCTime year interpretation

UTCTime years in the range 50-99 must map to 1950-1999, but the parser unconditionally added 2000, producing dates 100 years in the future. This caused verify() to accept certificates whose validity actually expired decades ago. Change that to match what OpenSSL, BoringSSL, etc. do

1 files changed, 13 insertions(+), 1 deletions(-)

lib/std/crypto/Certificate.zig+13-1
......@@ -580,7 +580,10 @@ pub fn parseTime(cert: Certificate, elem: der.Element) ParseTimeError!u64 {
580580 return error.CertificateTimeInvalid;
581581
582582 return Date.toSeconds(.{
583 .year = @as(u16, 2000) + try parseTimeDigits(bytes[0..2], 0, 99),
583 .year = blk: {
584 const year = try parseTimeDigits(bytes[0..2], 0, 99);
585 break :blk if (year < 50) @as(u16, 2000) + year else @as(u16, 1900) + year;
586 },
584587 .month = try parseTimeDigits(bytes[2..4], 1, 12),
585588 .day = try parseTimeDigits(bytes[4..6], 1, 31),
586589 .hour = try parseTimeDigits(bytes[6..8], 0, 23),
......@@ -670,6 +673,15 @@ pub fn parseTimeDigits(text: *const [2]u8, min: u8, max: u8) !u8 {
670673 return @intCast(result);
671674}
672675
676test "parseTime UTCTime year mapping per RFC 5280" {
677 const utc_time_id: der.Identifier = .{ .tag = .utc_time, .pc = .primitive, .class = .universal };
678 const elem = der.Element{ .identifier = utc_time_id, .slice = .{ .start = 0, .end = 13 } };
679 const cert49 = Certificate{ .buffer = "490101000000Z", .index = 0 };
680 try std.testing.expectEqual(@as(u64, 2493072000), try cert49.parseTime(elem));
681 const cert99 = Certificate{ .buffer = "990101000000Z", .index = 0 };
682 try std.testing.expectEqual(@as(u64, 915148800), try cert99.parseTime(elem));
683}
684
673685test parseTimeDigits {
674686 const expectEqual = std.testing.expectEqual;
675687 try expectEqual(@as(u8, 0), try parseTimeDigits("00", 0, 99));