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.
This commit is contained in:
David Rubin
2026-01-05 09:33:51 -08:00
committed by Andrew Kelley
parent 9c55776d25
commit 8b71ec6db7
+24 -9
View File
@@ -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 {