master
  1//! ASN.1 types for public consumption.
  2const std = @import("std");
  3pub const der = @import("./asn1/der.zig");
  4pub const Oid = @import("./asn1/Oid.zig");
  5
  6pub const Index = u32;
  7
  8pub const Tag = struct {
  9    number: Number,
 10    /// Whether this ASN.1 type contains other ASN.1 types.
 11    constructed: bool,
 12    class: Class,
 13
 14    /// These values apply to class == .universal.
 15    pub const Number = enum(u16) {
 16        // 0 is reserved by spec
 17        boolean = 1,
 18        integer = 2,
 19        bitstring = 3,
 20        octetstring = 4,
 21        null = 5,
 22        oid = 6,
 23        object_descriptor = 7,
 24        real = 9,
 25        enumerated = 10,
 26        embedded = 11,
 27        string_utf8 = 12,
 28        oid_relative = 13,
 29        time = 14,
 30        // 15 is reserved to mean that the tag is >= 32
 31        sequence = 16,
 32        /// Elements may appear in any order.
 33        sequence_of = 17,
 34        string_numeric = 18,
 35        string_printable = 19,
 36        string_teletex = 20,
 37        string_videotex = 21,
 38        string_ia5 = 22,
 39        utc_time = 23,
 40        generalized_time = 24,
 41        string_graphic = 25,
 42        string_visible = 26,
 43        string_general = 27,
 44        string_universal = 28,
 45        string_char = 29,
 46        string_bmp = 30,
 47        date = 31,
 48        time_of_day = 32,
 49        date_time = 33,
 50        duration = 34,
 51        /// IRI = Internationalized Resource Identifier
 52        oid_iri = 35,
 53        oid_iri_relative = 36,
 54        _,
 55    };
 56
 57    pub const Class = enum(u2) {
 58        universal,
 59        application,
 60        context_specific,
 61        private,
 62    };
 63
 64    pub fn init(number: Tag.Number, constructed: bool, class: Tag.Class) Tag {
 65        return .{ .number = number, .constructed = constructed, .class = class };
 66    }
 67
 68    pub fn universal(number: Tag.Number, constructed: bool) Tag {
 69        return .{ .number = number, .constructed = constructed, .class = .universal };
 70    }
 71
 72    pub fn decode(reader: *std.Io.Reader) !Tag {
 73        const tag1: FirstTag = @bitCast(try reader.takeByte());
 74        var number: u14 = tag1.number;
 75
 76        if (tag1.number == 15) {
 77            const tag2: NextTag = @bitCast(try reader.takeByte());
 78            number = tag2.number;
 79            if (tag2.continues) {
 80                const tag3: NextTag = @bitCast(try reader.takeByte());
 81                number = (number << 7) + tag3.number;
 82                if (tag3.continues) return error.InvalidLength;
 83            }
 84        }
 85
 86        return Tag{
 87            .number = @enumFromInt(number),
 88            .constructed = tag1.constructed,
 89            .class = tag1.class,
 90        };
 91    }
 92
 93    pub fn encode(self: Tag, writer: *std.Io.Writer) @TypeOf(writer).Error!void {
 94        var tag1 = FirstTag{
 95            .number = undefined,
 96            .constructed = self.constructed,
 97            .class = self.class,
 98        };
 99
100        var buffer: [3]u8 = undefined;
101        var writer2: std.Io.Writer = .init(&buffer);
102
103        switch (@intFromEnum(self.number)) {
104            0...std.math.maxInt(u5) => |n| {
105                tag1.number = @intCast(n);
106                writer2.writeByte(@bitCast(tag1)) catch unreachable;
107            },
108            std.math.maxInt(u5) + 1...std.math.maxInt(u7) => |n| {
109                tag1.number = 15;
110                const tag2 = NextTag{ .number = @intCast(n), .continues = false };
111                writer2.writeByte(@bitCast(tag1)) catch unreachable;
112                writer2.writeByte(@bitCast(tag2)) catch unreachable;
113            },
114            else => |n| {
115                tag1.number = 15;
116                const tag2 = NextTag{ .number = @intCast(n >> 7), .continues = true };
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        _ = try writer.write(writer2.buffered());
125    }
126
127    const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class };
128    const NextTag = packed struct(u8) { number: u7, continues: bool };
129
130    pub fn toExpected(self: Tag) ExpectedTag {
131        return ExpectedTag{
132            .number = self.number,
133            .constructed = self.constructed,
134            .class = self.class,
135        };
136    }
137
138    pub fn fromZig(comptime T: type) Tag {
139        switch (@typeInfo(T)) {
140            .@"struct", .@"enum", .@"union" => {
141                if (@hasDecl(T, "asn1_tag")) return T.asn1_tag;
142            },
143            else => {},
144        }
145
146        switch (@typeInfo(T)) {
147            .@"struct", .@"union" => return universal(.sequence, true),
148            .bool => return universal(.boolean, false),
149            .int => return universal(.integer, false),
150            .@"enum" => |e| {
151                if (@hasDecl(T, "oids")) return Oid.asn1_tag;
152                return universal(if (e.is_exhaustive) .enumerated else .integer, false);
153            },
154            .optional => |o| return fromZig(o.child),
155            .null => return universal(.null, false),
156            else => @compileError("cannot map Zig type to asn1_tag " ++ @typeName(T)),
157        }
158    }
159};
160
161test Tag {
162    const buf = [_]u8{0xa3};
163    var reader: std.Io.Reader = .fixed(&buf);
164    const t = Tag.decode(&reader);
165    try std.testing.expectEqual(Tag.init(@enumFromInt(3), true, .context_specific), t);
166}
167
168/// A decoded view.
169pub const Element = struct {
170    tag: Tag,
171    slice: Slice,
172
173    pub const Slice = struct {
174        start: Index,
175        end: Index,
176
177        pub fn len(self: Slice) Index {
178            return self.end - self.start;
179        }
180
181        pub fn view(self: Slice, bytes: []const u8) []const u8 {
182            return bytes[self.start..self.end];
183        }
184    };
185
186    pub const DecodeError = error{ InvalidLength, EndOfStream };
187
188    /// Safely decode a DER/BER/CER element at `index`:
189    /// - Ensures length uses shortest form
190    /// - Ensures length is within `bytes`
191    /// - Ensures length is less than `std.math.maxInt(Index)`
192    pub fn decode(bytes: []const u8, index: Index) DecodeError!Element {
193        var reader: std.Io.Reader = .fixed(bytes[index..]);
194
195        const tag = try Tag.decode(&reader);
196        const size_or_len_size = try reader.takeByte();
197
198        var start = index + 2;
199        var end = start + size_or_len_size;
200        // short form between 0-127
201        if (size_or_len_size < 128) {
202            if (end > bytes.len) return error.InvalidLength;
203        } else {
204            // long form between 0 and std.math.maxInt(u1024)
205            const len_size: u7 = @truncate(size_or_len_size);
206            start += len_size;
207            if (len_size > @sizeOf(Index)) return error.InvalidLength;
208
209            const len = try reader.takeVarInt(Index, .big, len_size);
210            if (len < 128) return error.InvalidLength; // should have used short form
211
212            end = std.math.add(Index, start, len) catch return error.InvalidLength;
213            if (end > bytes.len) return error.InvalidLength;
214        }
215
216        return Element{ .tag = tag, .slice = Slice{ .start = start, .end = end } };
217    }
218};
219
220test Element {
221    const short_form = [_]u8{ 0x30, 0x03, 0x02, 0x01, 0x09 };
222    try std.testing.expectEqual(Element{
223        .tag = Tag.universal(.sequence, true),
224        .slice = Element.Slice{ .start = 2, .end = short_form.len },
225    }, Element.decode(&short_form, 0));
226
227    const long_form = [_]u8{ 0x30, 129, 129 } ++ [_]u8{0} ** 129;
228    try std.testing.expectEqual(Element{
229        .tag = Tag.universal(.sequence, true),
230        .slice = Element.Slice{ .start = 3, .end = long_form.len },
231    }, Element.decode(&long_form, 0));
232}
233
234/// For decoding.
235pub const ExpectedTag = struct {
236    number: ?Tag.Number = null,
237    constructed: ?bool = null,
238    class: ?Tag.Class = null,
239
240    pub fn init(number: ?Tag.Number, constructed: ?bool, class: ?Tag.Class) ExpectedTag {
241        return .{ .number = number, .constructed = constructed, .class = class };
242    }
243
244    pub fn primitive(number: ?Tag.Number) ExpectedTag {
245        return .{ .number = number, .constructed = false, .class = .universal };
246    }
247
248    pub fn match(self: ExpectedTag, tag: Tag) bool {
249        if (self.number) |e| {
250            if (tag.number != e) return false;
251        }
252        if (self.constructed) |e| {
253            if (tag.constructed != e) return false;
254        }
255        if (self.class) |e| {
256            if (tag.class != e) return false;
257        }
258        return true;
259    }
260};
261
262pub const FieldTag = struct {
263    number: std.meta.Tag(Tag.Number),
264    class: Tag.Class,
265    explicit: bool = true,
266
267    pub fn initExplicit(number: std.meta.Tag(Tag.Number), class: Tag.Class) FieldTag {
268        return .{ .number = number, .class = class, .explicit = true };
269    }
270
271    pub fn initImplicit(number: std.meta.Tag(Tag.Number), class: Tag.Class) FieldTag {
272        return .{ .number = number, .class = class, .explicit = false };
273    }
274
275    pub fn fromContainer(comptime Container: type, comptime field_name: []const u8) ?FieldTag {
276        if (@hasDecl(Container, "asn1_tags") and @hasField(@TypeOf(Container.asn1_tags), field_name)) {
277            return @field(Container.asn1_tags, field_name);
278        }
279
280        return null;
281    }
282
283    pub fn toTag(self: FieldTag) Tag {
284        return Tag.init(@enumFromInt(self.number), self.explicit, self.class);
285    }
286};
287
288pub const BitString = struct {
289    /// Number of bits in rightmost byte that are unused.
290    right_padding: u3 = 0,
291    bytes: []const u8,
292
293    pub fn bitLen(self: BitString) usize {
294        return self.bytes.len * 8 - self.right_padding;
295    }
296
297    const asn1_tag = Tag.universal(.bitstring, false);
298
299    pub fn decodeDer(decoder: *der.Decoder) !BitString {
300        const ele = try decoder.element(asn1_tag.toExpected());
301        const bytes = decoder.view(ele);
302
303        if (bytes.len < 1) return error.InvalidBitString;
304        const padding = bytes[0];
305        if (padding >= 8) return error.InvalidBitString;
306        const right_padding: u3 = @intCast(padding);
307
308        // DER requires that unused bits be zero.
309        if (@ctz(bytes[bytes.len - 1]) < right_padding) return error.InvalidBitString;
310
311        return BitString{ .bytes = bytes[1..], .right_padding = right_padding };
312    }
313
314    pub fn encodeDer(self: BitString, encoder: *der.Encoder) !void {
315        try encoder.writer().writeAll(self.bytes);
316        try encoder.writer().writeByte(self.right_padding);
317        try encoder.length(self.bytes.len + 1);
318        try encoder.tag(asn1_tag);
319    }
320};
321
322pub fn Opaque(comptime tag: Tag) type {
323    return struct {
324        bytes: []const u8,
325
326        pub fn decodeDer(decoder: *der.Decoder) !@This() {
327            const ele = try decoder.element(tag.toExpected());
328            if (tag.constructed) decoder.index = ele.slice.end;
329            return .{ .bytes = decoder.view(ele) };
330        }
331
332        pub fn encodeDer(self: @This(), encoder: *der.Encoder) !void {
333            try encoder.tagBytes(tag, self.bytes);
334        }
335    };
336}
337
338/// Use sparingly.
339pub const Any = struct {
340    tag: Tag,
341    bytes: []const u8,
342
343    pub fn decodeDer(decoder: *der.Decoder) !@This() {
344        const ele = try decoder.element(ExpectedTag{});
345        return .{ .tag = ele.tag, .bytes = decoder.view(ele) };
346    }
347
348    pub fn encodeDer(self: @This(), encoder: *der.Encoder) !void {
349        try encoder.tagBytes(self.tag, self.bytes);
350    }
351};
352
353test {
354    _ = der;
355    _ = Oid;
356    _ = @import("asn1/test.zig");
357}