zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit f6deade4e0033031d2161049dc62ab94c27209f4 (tree)
parent af1f91cadf0a2b04ea099334fe2008e526d4080b
Author: Frank Denis <github@pureftpd.org>
Date:   Sun,  3 May 2026 17:30:21 +0200

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

Diffstat:
Mlib/std/crypto/codecs/asn1.zig | 102++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
1 file changed, 67 insertions(+), 35 deletions(-)

diff --git a/lib/std/crypto/codecs/asn1.zig b/lib/std/crypto/codecs/asn1.zig @@ -71,16 +71,18 @@ pub const Tag = struct { pub fn decode(reader: *std.Io.Reader) !Tag { const tag1: FirstTag = @bitCast(try reader.takeByte()); - var number: u14 = tag1.number; - - if (tag1.number == 31) { - const tag2: NextTag = @bitCast(try reader.takeByte()); - number = tag2.number; - if (tag2.continues) { - const tag3: NextTag = @bitCast(try reader.takeByte()); - number = (number << 7) + tag3.number; - if (tag3.continues) return error.EndOfStream; - } + var number: std.meta.Tag(Tag.Number) = tag1.number; + + if (tag1.number == high_tag_marker) { + number = 0; + for (0..max_continuations) |i| { + const next: NextTag = @bitCast(try reader.takeByte()); + if (i == 0 and next.number == 0) return error.InvalidEncoding; + number = std.math.shlExact(@TypeOf(number), number, 7) catch return error.InvalidEncoding; + number |= next.number; + if (!next.continues) break; + } else return error.InvalidEncoding; + if (number < high_tag_marker) return error.InvalidEncoding; } return Tag{ @@ -90,40 +92,51 @@ pub const Tag = struct { }; } - pub fn encode(self: Tag, writer: *std.Io.Writer) @TypeOf(writer).Error!void { - var tag1 = FirstTag{ + pub fn encodeToSlice(self: Tag, buf: *[max_encoded_len]u8) []const u8 { + const n = @intFromEnum(self.number); + var tag1: FirstTag = .{ .number = undefined, .constructed = self.constructed, .class = self.class, }; - var buffer: [3]u8 = undefined; - var writer2: std.Io.Writer = .init(&buffer); + if (n < high_tag_marker) { + tag1.number = @intCast(n); + buf[0] = @bitCast(tag1); + return buf[0..1]; + } - switch (@intFromEnum(self.number)) { - 0...std.math.maxInt(u5) => |n| { - tag1.number = @intCast(n); - writer2.writeByte(@bitCast(tag1)) catch unreachable; - }, - std.math.maxInt(u5) + 1...std.math.maxInt(u7) => |n| { - tag1.number = 15; - const tag2 = NextTag{ .number = @intCast(n), .continues = false }; - writer2.writeByte(@bitCast(tag1)) catch unreachable; - writer2.writeByte(@bitCast(tag2)) catch unreachable; - }, - else => |n| { - tag1.number = 15; - const tag2 = NextTag{ .number = @intCast(n >> 7), .continues = true }; - const tag3 = NextTag{ .number = @truncate(n), .continues = false }; - writer2.writeByte(@bitCast(tag1)) catch unreachable; - writer2.writeByte(@bitCast(tag2)) catch unreachable; - writer2.writeByte(@bitCast(tag3)) catch unreachable; - }, + tag1.number = high_tag_marker; + buf[0] = @bitCast(tag1); + + const bits_used = @bitSizeOf(@TypeOf(n)) - @clz(n); + const len = std.math.divCeil(usize, bits_used, 7) catch unreachable; + + var remaining = n; + var i = len; + while (i > 0) : (i -= 1) { + buf[i] = @bitCast(NextTag{ + .number = @truncate(remaining), + .continues = i != len, + }); + remaining >>= 7; } + return buf[0 .. 1 + len]; + } - _ = try writer.write(writer2.buffered()); + pub fn encode(self: Tag, writer: *std.Io.Writer) std.Io.Writer.Error!void { + var buf: [max_encoded_len]u8 = undefined; + try writer.writeAll(self.encodeToSlice(&buf)); } + pub const max_encoded_len = 1 + (std.math.divCeil( + comptime_int, + @bitSizeOf(std.meta.Tag(Tag.Number)), + 7, + ) catch unreachable); + const max_continuations = max_encoded_len - 1; + const high_tag_marker = std.math.maxInt(u5); + const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class }; const NextTag = packed struct(u8) { number: u7, continues: bool }; @@ -165,6 +178,24 @@ test Tag { try std.testing.expectEqual(Tag.init(@enumFromInt(3), true, .context_specific), t); } +test "Tag.encode/decode round trip" { + for ([_]u16{ 0, 30, 31, 32, 127, 128, 16383, 16384, 65535 }) |n| { + const tag = Tag.init(@enumFromInt(n), false, .universal); + var buf: [Tag.max_encoded_len]u8 = undefined; + const encoded = tag.encodeToSlice(&buf); + var reader: std.Io.Reader = .fixed(encoded); + try std.testing.expectEqual(tag, try Tag.decode(&reader)); + try std.testing.expectEqual(encoded.len, reader.seek); + } +} + +test "Tag.decode rejects non-minimal high-tag form" { + for ([_][]const u8{ &.{ 0x1f, 0x1e }, &.{ 0x1f, 0x80, 0x01 } }) |bytes| { + var reader: std.Io.Reader = .fixed(bytes); + try std.testing.expectError(error.InvalidEncoding, Tag.decode(&reader)); + } +} + /// A decoded view. pub const Element = struct { tag: Tag, @@ -183,13 +214,14 @@ pub const Element = struct { } }; - pub const DecodeError = error{EndOfStream}; + pub const DecodeError = error{ EndOfStream, InvalidEncoding }; /// Safely decode a DER/BER/CER element at `index`: /// - Ensures length uses shortest form /// - Ensures length is within `bytes` /// - Ensures length is less than `std.math.maxInt(Index)` pub fn decode(bytes: []const u8, index: Index) DecodeError!Element { + if (index > bytes.len) return error.EndOfStream; var reader: std.Io.Reader = .fixed(bytes[index..]); const tag = Tag.decode(&reader) catch |err| switch (err) {