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 {
651651};
652652
653653pub fn parseTimeDigits(text: *const [2]u8, min: u8, max: u8) !u8 {
654 const nn: @Vector(2, u16) = .{ text[0], text[1] };
655 const zero: @Vector(2, u16) = .{ '0', '0' };
656 const mm: @Vector(2, u16) = .{ 10, 1 };
657 const result = @reduce(.Add, (nn -% zero) *% mm);
654 const V = @Vector(2, u16);
655 const bytes: V = text.*;
656 const zero: V = @splat('0');
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);
658664 if (result < min) return error.CertificateTimeInvalid;
659665 if (result > max) return error.CertificateTimeInvalid;
660666 return @intCast(result);
......@@ -670,14 +676,20 @@ test parseTimeDigits {
670676 try expectError(error.CertificateTimeInvalid, parseTimeDigits("13", 1, 12));
671677 try expectError(error.CertificateTimeInvalid, parseTimeDigits("00", 1, 12));
672678 try expectError(error.CertificateTimeInvalid, parseTimeDigits("Di", 0, 99));
679 try expectError(error.CertificateTimeInvalid, parseTimeDigits("0:", 1, 31));
673680}
674681
675682pub fn parseYear4(text: *const [4]u8) !u16 {
676 const nnnn: @Vector(4, u32) = .{ text[0], text[1], text[2], text[3] };
677 const zero: @Vector(4, u32) = .{ '0', '0', '0', '0' };
678 const mmmm: @Vector(4, u32) = .{ 1000, 100, 10, 1 };
679 const result = @reduce(.Add, (nnnn -% zero) *% mmmm);
680 if (result > 9999) return error.CertificateTimeInvalid;
683 const V = @Vector(4, u32);
684 const bytes: V = text.*;
685 const zero: V = @splat('0');
686 const mmmm: V = .{ 1000, 100, 10, 1 };
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);
681693 return @intCast(result);
682694}
683695
......@@ -691,6 +703,9 @@ test parseYear4 {
691703 try expectError(error.CertificateTimeInvalid, parseYear4("999b"));
692704 try expectError(error.CertificateTimeInvalid, parseYear4("crap"));
693705 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"));
694709}
695710
696711pub fn parseAlgorithm(bytes: []const u8, element: der.Element) ParseEnumError!Algorithm {