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
This commit is contained in:
Frank Denis
2026-04-20 12:13:52 +02:00
committed by Andrew Kelley
parent 525aff6048
commit 98ddebc380
+13 -1
View File
@@ -580,7 +580,10 @@ pub fn parseTime(cert: Certificate, elem: der.Element) ParseTimeError!u64 {
return error.CertificateTimeInvalid;
return Date.toSeconds(.{
.year = @as(u16, 2000) + try parseTimeDigits(bytes[0..2], 0, 99),
.year = blk: {
const year = try parseTimeDigits(bytes[0..2], 0, 99);
break :blk if (year < 50) @as(u16, 2000) + year else @as(u16, 1900) + year;
},
.month = try parseTimeDigits(bytes[2..4], 1, 12),
.day = try parseTimeDigits(bytes[4..6], 1, 31),
.hour = try parseTimeDigits(bytes[6..8], 0, 23),
@@ -670,6 +673,15 @@ pub fn parseTimeDigits(text: *const [2]u8, min: u8, max: u8) !u8 {
return @intCast(result);
}
test "parseTime UTCTime year mapping per RFC 5280" {
const utc_time_id: der.Identifier = .{ .tag = .utc_time, .pc = .primitive, .class = .universal };
const elem = der.Element{ .identifier = utc_time_id, .slice = .{ .start = 0, .end = 13 } };
const cert49 = Certificate{ .buffer = "490101000000Z", .index = 0 };
try std.testing.expectEqual(@as(u64, 2493072000), try cert49.parseTime(elem));
const cert99 = Certificate{ .buffer = "990101000000Z", .index = 0 };
try std.testing.expectEqual(@as(u64, 915148800), try cert99.parseTime(elem));
}
test parseTimeDigits {
const expectEqual = std.testing.expectEqual;
try expectEqual(@as(u8, 0), try parseTimeDigits("00", 0, 99));