From 8b71ec6db700dc7af22ce0729991bce846ae6d76 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Mon, 5 Jan 2026 09:33:51 -0800 Subject: [PATCH] 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. --- lib/std/crypto/Certificate.zig | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/std/crypto/Certificate.zig b/lib/std/crypto/Certificate.zig index c7fb372b8b..761cf49ade 100644 --- a/lib/std/crypto/Certificate.zig +++ b/lib/std/crypto/Certificate.zig @@ -651,10 +651,16 @@ const Date = struct { }; pub fn parseTimeDigits(text: *const [2]u8, min: u8, max: u8) !u8 { - const nn: @Vector(2, u16) = .{ text[0], text[1] }; - const zero: @Vector(2, u16) = .{ '0', '0' }; - const mm: @Vector(2, u16) = .{ 10, 1 }; - const result = @reduce(.Add, (nn -% zero) *% mm); + const V = @Vector(2, u16); + const bytes: V = text.*; + const zero: V = @splat('0'); + const mm: V = .{ 10, 1 }; + const d = bytes -% zero; + if (@reduce(.Or, d > @as(V, @splat(9)))) { + @branchHint(.unlikely); + return error.CertificateTimeInvalid; + } + const result = @reduce(.Add, d *% mm); if (result < min) return error.CertificateTimeInvalid; if (result > max) return error.CertificateTimeInvalid; return @intCast(result); @@ -670,14 +676,20 @@ test parseTimeDigits { try expectError(error.CertificateTimeInvalid, parseTimeDigits("13", 1, 12)); try expectError(error.CertificateTimeInvalid, parseTimeDigits("00", 1, 12)); try expectError(error.CertificateTimeInvalid, parseTimeDigits("Di", 0, 99)); + try expectError(error.CertificateTimeInvalid, parseTimeDigits("0:", 1, 31)); } pub fn parseYear4(text: *const [4]u8) !u16 { - const nnnn: @Vector(4, u32) = .{ text[0], text[1], text[2], text[3] }; - const zero: @Vector(4, u32) = .{ '0', '0', '0', '0' }; - const mmmm: @Vector(4, u32) = .{ 1000, 100, 10, 1 }; - const result = @reduce(.Add, (nnnn -% zero) *% mmmm); - if (result > 9999) return error.CertificateTimeInvalid; + const V = @Vector(4, u32); + const bytes: V = text.*; + const zero: V = @splat('0'); + const mmmm: V = .{ 1000, 100, 10, 1 }; + const d = bytes -% zero; + if (@reduce(.Or, d > @as(V, @splat(9)))) { + @branchHint(.unlikely); + return error.CertificateTimeInvalid; + } + const result = @reduce(.Add, d *% mmmm); return @intCast(result); } @@ -691,6 +703,9 @@ test parseYear4 { try expectError(error.CertificateTimeInvalid, parseYear4("999b")); try expectError(error.CertificateTimeInvalid, parseYear4("crap")); try expectError(error.CertificateTimeInvalid, parseYear4("r:bQ")); + try expectError(error.CertificateTimeInvalid, parseYear4("000:")); + try expectError(error.CertificateTimeInvalid, parseYear4("0???")); + try expectError(error.CertificateTimeInvalid, parseYear4("*zig")); } pub fn parseAlgorithm(bytes: []const u8, element: der.Element) ParseEnumError!Algorithm {