commit 761f8d2f8a244b01f303a0a03bfda9b56c06e18f (tree)
parent 5ab3a21e390384672bc58171c9985d2d1d11b46e
Author: Frank Denis <github@pureftpd.org>
Date: Sun, 3 May 2026 17:48:20 +0200
der.Decoder: fix int slice and sign-extension
Diffstat:
2 files changed, 40 insertions(+), 11 deletions(-)
diff --git a/lib/std/crypto/codecs/asn1/der.zig b/lib/std/crypto/codecs/asn1/der.zig
@@ -49,6 +49,23 @@ test decode {
try std.testing.expectEqualDeep(test_case.value, decoded);
}
+test "integer round trip across signed and unsigned boundaries" {
+ const allocator = std.testing.allocator;
+ inline for (.{ u8, u16, u32, i8, i16, i32 }) |T| {
+ const cases = comptime blk: {
+ const min = std.math.minInt(T);
+ const max = std.math.maxInt(T);
+ break :blk [_]T{ 0, 1, max, min, @divTrunc(max, 2), @divTrunc(min, 2) };
+ };
+ for (cases) |value| {
+ const buf = try encode(allocator, value);
+ defer allocator.free(buf);
+ const decoded = try decode(T, buf);
+ try std.testing.expectEqual(value, decoded);
+ }
+ }
+}
+
test {
_ = Decoder;
_ = Encoder;
diff --git a/lib/std/crypto/codecs/asn1/der/Decoder.zig b/lib/std/crypto/codecs/asn1/der/Decoder.zig
@@ -111,21 +111,23 @@ pub fn view(self: Decoder, elem: Element) []const u8 {
}
fn int(comptime T: type, value: []const u8) error{ NonCanonical, LargeValue }!T {
- if (@typeInfo(T).int.bits % 8 != 0) @compileError("T must be byte aligned");
-
- var bytes = value;
- if (bytes.len >= 2) {
- if (bytes[0] == 0) {
- if (@clz(bytes[1]) > 0) return error.NonCanonical;
- bytes.ptr += 1;
- }
- if (bytes[0] == 0xff and @clz(bytes[1]) == 0) return error.NonCanonical;
+ const info = @typeInfo(T).int;
+ if (info.bits % 8 != 0) @compileError("T must be byte aligned");
+
+ if (value.len == 0) return error.NonCanonical;
+ if (value.len >= 2) {
+ if (value[0] == 0x00 and value[1] & 0x80 == 0) return error.NonCanonical;
+ if (value[0] == 0xff and value[1] & 0x80 != 0) return error.NonCanonical;
}
+ const had_sign_byte = value.len >= 2 and value[0] == 0x00;
+ const bytes = if (had_sign_byte) value[1..] else value;
if (bytes.len > @sizeOf(T)) return error.LargeValue;
- if (@sizeOf(T) == 1) return @bitCast(bytes[0]);
- return std.mem.readVarInt(T, bytes, .big);
+ const sign_extend = info.signedness == .signed and !had_sign_byte and bytes[0] & 0x80 != 0;
+ var buf: [@sizeOf(T)]u8 = @splat(if (sign_extend) 0xff else 0);
+ @memcpy(buf[buf.len - bytes.len ..], bytes);
+ return std.mem.readInt(T, &buf, .big);
}
test int {
@@ -136,6 +138,16 @@ test int {
const big = [_]u8{ 0xef, 0xff };
try expectError(error.LargeValue, int(u8, &big));
try expectEqual(0xefff, int(u16, &big));
+
+ try expectEqual(@as(u16, 255), try int(u16, &.{ 0x00, 0xff }));
+ try expectEqual(@as(u16, 0x8000), try int(u16, &.{ 0x00, 0x80, 0x00 }));
+
+ try expectEqual(@as(i8, -1), try int(i8, &.{0xff}));
+ try expectEqual(@as(i16, -1), try int(i16, &.{0xff}));
+ try expectEqual(@as(i16, -128), try int(i16, &.{0x80}));
+ try expectEqual(@as(i16, -129), try int(i16, &.{ 0xff, 0x7f }));
+ try expectEqual(@as(i16, 255), try int(i16, &.{ 0x00, 0xff }));
+ try expectEqual(@as(i32, 0x7fffffff), try int(i32, &.{ 0x7f, 0xff, 0xff, 0xff }));
}
test Decoder {