| ... | @@ -71,16 +71,18 @@ pub const Tag = struct { | ... | @@ -71,16 +71,18 @@ pub const Tag = struct { |
| 71 | | 71 | |
| 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; |
| 75 | | 75 | |
| 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 | } |
| 85 | | 87 | |
| 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 | } |
| 92 | | 94 | |
| 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 | }; |
| 99 | | 102 | |
| 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 | } |
| 102 | | 108 | |
| 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 | } |
| 123 | | 126 | |
| 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 | } |
| 126 | | 131 | |
| | 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 }; |
| 129 | | 142 | |
| ... | @@ -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 | } |
| 167 | | 180 | |
| | 181 | test "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 | |
| | 192 | test "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. |
| 169 | pub const Element = struct { | 200 | pub 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 | }; |
| 185 | | 216 | |
| 186 | pub const DecodeError = error{EndOfStream}; | 217 | pub const DecodeError = error{ EndOfStream, InvalidEncoding }; |
| 187 | | 218 | |
| 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 form | 220 | /// - 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..]); |
| 194 | | 226 | |
| 195 | const tag = Tag.decode(&reader) catch |err| switch (err) { | 227 | const tag = Tag.decode(&reader) catch |err| switch (err) { |