authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-05-03 17:30:21+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
logf6deade4e0033031d2161049dc62ab94c27209f4
treeab48e72918d7195db1dbab5526b0bd4880dceb21
parentaf1f91cadf0a2b04ea099334fe2008e526d4080b

std.crypto.codecs.asn1: fix high-tag-number encoding

The encoder was writing 15 in the low five bits to mark a high tag number insetad of 31. Emit the correct marker and as many continuation bytes as the tag number requires, and expose encodeToSlice so a Writer is not needed any more. And return proper errors. Fixes #32069 and supersedes #32139

1 files changed, 67 insertions(+), 35 deletions(-)

lib/std/crypto/codecs/asn1.zig+67-35
...@@ -71,16 +71,18 @@ pub const Tag = struct {...@@ -71,16 +71,18 @@ pub const Tag = struct {
7171
72 pub fn decode(reader: *std.Io.Reader) !Tag {72 pub fn decode(reader: *std.Io.Reader) !Tag {
73 const tag1: FirstTag = @bitCast(try reader.takeByte());73 const tag1: FirstTag = @bitCast(try reader.takeByte());
74 var number: u14 = tag1.number;74 var number: std.meta.Tag(Tag.Number) = tag1.number;
7575
76 if (tag1.number == 31) {76 if (tag1.number == high_tag_marker) {
77 const tag2: NextTag = @bitCast(try reader.takeByte());77 number = 0;
78 number = tag2.number;78 for (0..max_continuations) |i| {
79 if (tag2.continues) {79 const next: NextTag = @bitCast(try reader.takeByte());
80 const tag3: NextTag = @bitCast(try reader.takeByte());80 if (i == 0 and next.number == 0) return error.InvalidEncoding;
81 number = (number << 7) + tag3.number;81 number = std.math.shlExact(@TypeOf(number), number, 7) catch return error.InvalidEncoding;
82 if (tag3.continues) return error.EndOfStream;82 number |= next.number;
83 }83 if (!next.continues) break;
84 } else return error.InvalidEncoding;
85 if (number < high_tag_marker) return error.InvalidEncoding;
84 }86 }
8587
86 return Tag{88 return Tag{
...@@ -90,40 +92,51 @@ pub const Tag = struct {...@@ -90,40 +92,51 @@ pub const Tag = struct {
90 };92 };
91 }93 }
9294
93 pub fn encode(self: Tag, writer: *std.Io.Writer) @TypeOf(writer).Error!void {95 pub fn encodeToSlice(self: Tag, buf: *[max_encoded_len]u8) []const u8 {
94 var tag1 = FirstTag{96 const n = @intFromEnum(self.number);
97 var tag1: FirstTag = .{
95 .number = undefined,98 .number = undefined,
96 .constructed = self.constructed,99 .constructed = self.constructed,
97 .class = self.class,100 .class = self.class,
98 };101 };
99102
100 var buffer: [3]u8 = undefined;103 if (n < high_tag_marker) {
101 var writer2: std.Io.Writer = .init(&buffer);104 tag1.number = @intCast(n);
105 buf[0] = @bitCast(tag1);
106 return buf[0..1];
107 }
102108
103 switch (@intFromEnum(self.number)) {109 tag1.number = high_tag_marker;
104 0...std.math.maxInt(u5) => |n| {110 buf[0] = @bitCast(tag1);
105 tag1.number = @intCast(n);111
106 writer2.writeByte(@bitCast(tag1)) catch unreachable;112 const bits_used = @bitSizeOf(@TypeOf(n)) - @clz(n);
107 },113 const len = std.math.divCeil(usize, bits_used, 7) catch unreachable;
108 std.math.maxInt(u5) + 1...std.math.maxInt(u7) => |n| {114
109 tag1.number = 15;115 var remaining = n;
110 const tag2 = NextTag{ .number = @intCast(n), .continues = false };116 var i = len;
111 writer2.writeByte(@bitCast(tag1)) catch unreachable;117 while (i > 0) : (i -= 1) {
112 writer2.writeByte(@bitCast(tag2)) catch unreachable;118 buf[i] = @bitCast(NextTag{
113 },119 .number = @truncate(remaining),
114 else => |n| {120 .continues = i != len,
115 tag1.number = 15;121 });
116 const tag2 = NextTag{ .number = @intCast(n >> 7), .continues = true };122 remaining >>= 7;
117 const tag3 = NextTag{ .number = @truncate(n), .continues = false };
118 writer2.writeByte(@bitCast(tag1)) catch unreachable;
119 writer2.writeByte(@bitCast(tag2)) catch unreachable;
120 writer2.writeByte(@bitCast(tag3)) catch unreachable;
121 },
122 }123 }
124 return buf[0 .. 1 + len];
125 }
123126
124 _ = try writer.write(writer2.buffered());127 pub fn encode(self: Tag, writer: *std.Io.Writer) std.Io.Writer.Error!void {
128 var buf: [max_encoded_len]u8 = undefined;
129 try writer.writeAll(self.encodeToSlice(&buf));
125 }130 }
126131
132 pub const max_encoded_len = 1 + (std.math.divCeil(
133 comptime_int,
134 @bitSizeOf(std.meta.Tag(Tag.Number)),
135 7,
136 ) catch unreachable);
137 const max_continuations = max_encoded_len - 1;
138 const high_tag_marker = std.math.maxInt(u5);
139
127 const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class };140 const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class };
128 const NextTag = packed struct(u8) { number: u7, continues: bool };141 const NextTag = packed struct(u8) { number: u7, continues: bool };
129142
...@@ -165,6 +178,24 @@ test Tag {...@@ -165,6 +178,24 @@ test Tag {
165 try std.testing.expectEqual(Tag.init(@enumFromInt(3), true, .context_specific), t);178 try std.testing.expectEqual(Tag.init(@enumFromInt(3), true, .context_specific), t);
166}179}
167180
181test "Tag.encode/decode round trip" {
182 for ([_]u16{ 0, 30, 31, 32, 127, 128, 16383, 16384, 65535 }) |n| {
183 const tag = Tag.init(@enumFromInt(n), false, .universal);
184 var buf: [Tag.max_encoded_len]u8 = undefined;
185 const encoded = tag.encodeToSlice(&buf);
186 var reader: std.Io.Reader = .fixed(encoded);
187 try std.testing.expectEqual(tag, try Tag.decode(&reader));
188 try std.testing.expectEqual(encoded.len, reader.seek);
189 }
190}
191
192test "Tag.decode rejects non-minimal high-tag form" {
193 for ([_][]const u8{ &.{ 0x1f, 0x1e }, &.{ 0x1f, 0x80, 0x01 } }) |bytes| {
194 var reader: std.Io.Reader = .fixed(bytes);
195 try std.testing.expectError(error.InvalidEncoding, Tag.decode(&reader));
196 }
197}
198
168/// A decoded view.199/// A decoded view.
169pub const Element = struct {200pub const Element = struct {
170 tag: Tag,201 tag: Tag,
...@@ -183,13 +214,14 @@ pub const Element = struct {...@@ -183,13 +214,14 @@ pub const Element = struct {
183 }214 }
184 };215 };
185216
186 pub const DecodeError = error{EndOfStream};217 pub const DecodeError = error{ EndOfStream, InvalidEncoding };
187218
188 /// Safely decode a DER/BER/CER element at `index`:219 /// Safely decode a DER/BER/CER element at `index`:
189 /// - Ensures length uses shortest form220 /// - Ensures length uses shortest form
190 /// - Ensures length is within `bytes`221 /// - Ensures length is within `bytes`
191 /// - Ensures length is less than `std.math.maxInt(Index)`222 /// - Ensures length is less than `std.math.maxInt(Index)`
192 pub fn decode(bytes: []const u8, index: Index) DecodeError!Element {223 pub fn decode(bytes: []const u8, index: Index) DecodeError!Element {
224 if (index > bytes.len) return error.EndOfStream;
193 var reader: std.Io.Reader = .fixed(bytes[index..]);225 var reader: std.Io.Reader = .fixed(bytes[index..]);
194226
195 const tag = Tag.decode(&reader) catch |err| switch (err) {227 const tag = Tag.decode(&reader) catch |err| switch (err) {