authorgravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2026-01-05 09:33:51-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-06 23:37:43+01:00
log8b71ec6db700dc7af22ce0729991bce846ae6d76
treea5755b1aadcca066ef2823a5af9ebf8763a21750
parent9c55776d259a4c29bd292a690f37678cecd62fda

crypto: correctly disallow non-digits in time

Previously these functions made the assumption that when performing a on the input digits, there could be no collisions between the less significant digits being larger than '9', and the upper digits being small enough to get past the checks. Now we perform a correct check across all of the digits to ensure they're in between '0'-'9', at a minimal cost, since all digits are checked in parallel.

1 files changed, 24 insertions(+), 9 deletions(-)

lib/std/crypto/Certificate.zig+24-9
...@@ -651,10 +651,16 @@ const Date = struct {...@@ -651,10 +651,16 @@ const Date = struct {
651};651};
652652
653pub fn parseTimeDigits(text: *const [2]u8, min: u8, max: u8) !u8 {653pub fn parseTimeDigits(text: *const [2]u8, min: u8, max: u8) !u8 {
654 const nn: @Vector(2, u16) = .{ text[0], text[1] };654 const V = @Vector(2, u16);
655 const zero: @Vector(2, u16) = .{ '0', '0' };655 const bytes: V = text.*;
656 const mm: @Vector(2, u16) = .{ 10, 1 };656 const zero: V = @splat('0');
657 const result = @reduce(.Add, (nn -% zero) *% mm);657 const mm: V = .{ 10, 1 };
658 const d = bytes -% zero;
659 if (@reduce(.Or, d > @as(V, @splat(9)))) {
660 @branchHint(.unlikely);
661 return error.CertificateTimeInvalid;
662 }
663 const result = @reduce(.Add, d *% mm);
658 if (result < min) return error.CertificateTimeInvalid;664 if (result < min) return error.CertificateTimeInvalid;
659 if (result > max) return error.CertificateTimeInvalid;665 if (result > max) return error.CertificateTimeInvalid;
660 return @intCast(result);666 return @intCast(result);
...@@ -670,14 +676,20 @@ test parseTimeDigits {...@@ -670,14 +676,20 @@ test parseTimeDigits {
670 try expectError(error.CertificateTimeInvalid, parseTimeDigits("13", 1, 12));676 try expectError(error.CertificateTimeInvalid, parseTimeDigits("13", 1, 12));
671 try expectError(error.CertificateTimeInvalid, parseTimeDigits("00", 1, 12));677 try expectError(error.CertificateTimeInvalid, parseTimeDigits("00", 1, 12));
672 try expectError(error.CertificateTimeInvalid, parseTimeDigits("Di", 0, 99));678 try expectError(error.CertificateTimeInvalid, parseTimeDigits("Di", 0, 99));
679 try expectError(error.CertificateTimeInvalid, parseTimeDigits("0:", 1, 31));
673}680}
674681
675pub fn parseYear4(text: *const [4]u8) !u16 {682pub fn parseYear4(text: *const [4]u8) !u16 {
676 const nnnn: @Vector(4, u32) = .{ text[0], text[1], text[2], text[3] };683 const V = @Vector(4, u32);
677 const zero: @Vector(4, u32) = .{ '0', '0', '0', '0' };684 const bytes: V = text.*;
678 const mmmm: @Vector(4, u32) = .{ 1000, 100, 10, 1 };685 const zero: V = @splat('0');
679 const result = @reduce(.Add, (nnnn -% zero) *% mmmm);686 const mmmm: V = .{ 1000, 100, 10, 1 };
680 if (result > 9999) return error.CertificateTimeInvalid;687 const d = bytes -% zero;
688 if (@reduce(.Or, d > @as(V, @splat(9)))) {
689 @branchHint(.unlikely);
690 return error.CertificateTimeInvalid;
691 }
692 const result = @reduce(.Add, d *% mmmm);
681 return @intCast(result);693 return @intCast(result);
682}694}
683695
...@@ -691,6 +703,9 @@ test parseYear4 {...@@ -691,6 +703,9 @@ test parseYear4 {
691 try expectError(error.CertificateTimeInvalid, parseYear4("999b"));703 try expectError(error.CertificateTimeInvalid, parseYear4("999b"));
692 try expectError(error.CertificateTimeInvalid, parseYear4("crap"));704 try expectError(error.CertificateTimeInvalid, parseYear4("crap"));
693 try expectError(error.CertificateTimeInvalid, parseYear4("r:bQ"));705 try expectError(error.CertificateTimeInvalid, parseYear4("r:bQ"));
706 try expectError(error.CertificateTimeInvalid, parseYear4("000:"));
707 try expectError(error.CertificateTimeInvalid, parseYear4("0???"));
708 try expectError(error.CertificateTimeInvalid, parseYear4("*zig"));
694}709}
695710
696pub fn parseAlgorithm(bytes: []const u8, element: der.Element) ParseEnumError!Algorithm {711pub fn parseAlgorithm(bytes: []const u8, element: der.Element) ParseEnumError!Algorithm {