authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-05-03 17:48:20+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-05-28 16:28:27+02:00
log761f8d2f8a244b01f303a0a03bfda9b56c06e18f
tree07c4699a7a3ded4f56f3774331aaa6f53c44b464
parent5ab3a21e390384672bc58171c9985d2d1d11b46e

der.Decoder: fix int slice and sign-extension


2 files changed, 40 insertions(+), 11 deletions(-)

lib/std/crypto/codecs/asn1/der.zig+17
...@@ -49,6 +49,23 @@ test decode {...@@ -49,6 +49,23 @@ test decode {
49 try std.testing.expectEqualDeep(test_case.value, decoded);49 try std.testing.expectEqualDeep(test_case.value, decoded);
50}50}
5151
52test "integer round trip across signed and unsigned boundaries" {
53 const allocator = std.testing.allocator;
54 inline for (.{ u8, u16, u32, i8, i16, i32 }) |T| {
55 const cases = comptime blk: {
56 const min = std.math.minInt(T);
57 const max = std.math.maxInt(T);
58 break :blk [_]T{ 0, 1, max, min, @divTrunc(max, 2), @divTrunc(min, 2) };
59 };
60 for (cases) |value| {
61 const buf = try encode(allocator, value);
62 defer allocator.free(buf);
63 const decoded = try decode(T, buf);
64 try std.testing.expectEqual(value, decoded);
65 }
66 }
67}
68
52test {69test {
53 _ = Decoder;70 _ = Decoder;
54 _ = Encoder;71 _ = Encoder;
lib/std/crypto/codecs/asn1/der/Decoder.zig+23-11
...@@ -111,21 +111,23 @@ pub fn view(self: Decoder, elem: Element) []const u8 {...@@ -111,21 +111,23 @@ pub fn view(self: Decoder, elem: Element) []const u8 {
111}111}
112112
113fn int(comptime T: type, value: []const u8) error{ NonCanonical, LargeValue }!T {113fn int(comptime T: type, value: []const u8) error{ NonCanonical, LargeValue }!T {
114 if (@typeInfo(T).int.bits % 8 != 0) @compileError("T must be byte aligned");114 const info = @typeInfo(T).int;
115115 if (info.bits % 8 != 0) @compileError("T must be byte aligned");
116 var bytes = value;116
117 if (bytes.len >= 2) {117 if (value.len == 0) return error.NonCanonical;
118 if (bytes[0] == 0) {118 if (value.len >= 2) {
119 if (@clz(bytes[1]) > 0) return error.NonCanonical;119 if (value[0] == 0x00 and value[1] & 0x80 == 0) return error.NonCanonical;
120 bytes.ptr += 1;120 if (value[0] == 0xff and value[1] & 0x80 != 0) return error.NonCanonical;
121 }
122 if (bytes[0] == 0xff and @clz(bytes[1]) == 0) return error.NonCanonical;
123 }121 }
124122
123 const had_sign_byte = value.len >= 2 and value[0] == 0x00;
124 const bytes = if (had_sign_byte) value[1..] else value;
125 if (bytes.len > @sizeOf(T)) return error.LargeValue;125 if (bytes.len > @sizeOf(T)) return error.LargeValue;
126 if (@sizeOf(T) == 1) return @bitCast(bytes[0]);
127126
128 return std.mem.readVarInt(T, bytes, .big);127 const sign_extend = info.signedness == .signed and !had_sign_byte and bytes[0] & 0x80 != 0;
128 var buf: [@sizeOf(T)]u8 = @splat(if (sign_extend) 0xff else 0);
129 @memcpy(buf[buf.len - bytes.len ..], bytes);
130 return std.mem.readInt(T, &buf, .big);
129}131}
130132
131test int {133test int {
...@@ -136,6 +138,16 @@ test int {...@@ -136,6 +138,16 @@ test int {
136 const big = [_]u8{ 0xef, 0xff };138 const big = [_]u8{ 0xef, 0xff };
137 try expectError(error.LargeValue, int(u8, &big));139 try expectError(error.LargeValue, int(u8, &big));
138 try expectEqual(0xefff, int(u16, &big));140 try expectEqual(0xefff, int(u16, &big));
141
142 try expectEqual(@as(u16, 255), try int(u16, &.{ 0x00, 0xff }));
143 try expectEqual(@as(u16, 0x8000), try int(u16, &.{ 0x00, 0x80, 0x00 }));
144
145 try expectEqual(@as(i8, -1), try int(i8, &.{0xff}));
146 try expectEqual(@as(i16, -1), try int(i16, &.{0xff}));
147 try expectEqual(@as(i16, -128), try int(i16, &.{0x80}));
148 try expectEqual(@as(i16, -129), try int(i16, &.{ 0xff, 0x7f }));
149 try expectEqual(@as(i16, 255), try int(i16, &.{ 0x00, 0xff }));
150 try expectEqual(@as(i32, 0x7fffffff), try int(i32, &.{ 0x7f, 0xff, 0xff, 0xff }));
139}151}
140152
141test Decoder {153test Decoder {