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 set = 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: std.meta.Tag(Tag.Number) = tag1.number;
75
76 if (tag1.number == high_tag_marker) {
77 number = 0;
78 for (0..max_continuations) |i| {
79 const next: NextTag = @bitCast(try reader.takeByte());
80 if (i == 0 and next.number == 0) return error.InvalidEncoding;
81 number = std.math.shlExact(@TypeOf(number), number, 7) catch return error.InvalidEncoding;
82 number |= next.number;
83 if (!next.continues) break;
84 } else return error.InvalidEncoding;
85 if (number < high_tag_marker) return error.InvalidEncoding;
86 }
87
88 return Tag{
89 .number = @fromBackingInt(@intCast(number)),
90 .constructed = tag1.constructed,
91 .class = tag1.class,
92 };
93 }
94
95 pub fn encodeToSlice(self: Tag, buf: *[max_encoded_len]u8) []const u8 {
96 const n = @backingInt(self.number);
97 var tag1: FirstTag = .{
98 .number = undefined,
99 .constructed = self.constructed,
100 .class = self.class,
101 };
102
103 if (n < high_tag_marker) {
104 tag1.number = @intCast(n);
105 buf[0] = @bitCast(tag1);
106 return buf[0..1];
107 }
108
109 tag1.number = high_tag_marker;
110 buf[0] = @bitCast(tag1);
111
112 const bits_used = @bitSizeOf(@TypeOf(n)) - @clz(n);
113 const len = std.math.divCeil(usize, bits_used, 7) catch unreachable;
114
115 var remaining = n;
116 var i = len;
117 while (i > 0) : (i -= 1) {
118 buf[i] = @bitCast(NextTag{
119 .number = @truncate(remaining),
120 .continues = i != len,
121 });
122 remaining >>= 7;
123 }
124 return buf[0 .. 1 + len];
125 }
126
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));
130 }
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
140 const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class };
141 const NextTag = packed struct(u8) { number: u7, continues: bool };
142
143 pub fn toExpected(self: Tag) ExpectedTag {
144 return ExpectedTag{
145 .number = self.number,
146 .constructed = self.constructed,
147 .class = self.class,
148 };
149 }
150
151 pub fn fromZig(comptime T: type) Tag {
152 switch (@typeInfo(T)) {
153 .@"struct", .@"enum", .@"union" => {
154 if (@hasDecl(T, "asn1_tag")) return T.asn1_tag;
155 },
156 else => {},
157 }
158
159 switch (@typeInfo(T)) {
160 .@"struct", .@"union" => return universal(.sequence, true),
161 .bool => return universal(.boolean, false),
162 .int => return universal(.integer, false),
163 .@"enum" => |e| {
164 if (@hasDecl(T, "oids")) return Oid.asn1_tag;
165 return universal(if (e.mode == .exhaustive) .enumerated else .integer, false);
166 },
167 .optional => |o| return fromZig(o.child),
168 .null => return universal(.null, false),
169 else => @compileError("cannot map Zig type to asn1_tag " ++ @typeName(T)),
170 }
171 }
172};
173
174test Tag {
175 const buf = [_]u8{0xa3};
176 var reader: std.Io.Reader = .fixed(&buf);
177 const t = Tag.decode(&reader);
178 try std.testing.expectEqual(Tag.init(@fromBackingInt(@intCast(3)), true, .context_specific), t);
179}
180
181test "Tag.encode produces the exact bytes from X.690" {
182 const cases = [_]struct { number: u16, expected: []const u8 }{
183 .{ .number = 0, .expected = &.{0x00} },
184 .{ .number = 30, .expected = &.{0x1e} },
185 .{ .number = 31, .expected = &.{ 0x1f, 0x1f } },
186 .{ .number = 127, .expected = &.{ 0x1f, 0x7f } },
187 .{ .number = 128, .expected = &.{ 0x1f, 0x81, 0x00 } },
188 .{ .number = 16383, .expected = &.{ 0x1f, 0xff, 0x7f } },
189 .{ .number = 16384, .expected = &.{ 0x1f, 0x81, 0x80, 0x00 } },
190 .{ .number = 65535, .expected = &.{ 0x1f, 0x83, 0xff, 0x7f } },
191 };
192 for (cases) |c| {
193 const tag = Tag.init(@fromBackingInt(@intCast(c.number)), false, .universal);
194 var buf: [Tag.max_encoded_len]u8 = undefined;
195 try std.testing.expectEqualSlices(u8, c.expected, tag.encodeToSlice(&buf));
196 }
197}
198
199test "Tag.encode/decode round trip" {
200 for ([_]u16{ 0, 30, 31, 32, 127, 128, 16383, 16384, 65535 }) |n| {
201 const tag = Tag.init(@fromBackingInt(@intCast(n)), false, .universal);
202 var buf: [Tag.max_encoded_len]u8 = undefined;
203 const encoded = tag.encodeToSlice(&buf);
204 var reader: std.Io.Reader = .fixed(encoded);
205 try std.testing.expectEqual(tag, try Tag.decode(&reader));
206 try std.testing.expectEqual(encoded.len, reader.seek);
207 }
208}
209
210test "Tag.decode rejects non-minimal high-tag form" {
211 for ([_][]const u8{ &.{ 0x1f, 0x1e }, &.{ 0x1f, 0x80, 0x01 } }) |bytes| {
212 var reader: std.Io.Reader = .fixed(bytes);
213 try std.testing.expectError(error.InvalidEncoding, Tag.decode(&reader));
214 }
215}
216
217/// A decoded view.
218pub const Element = struct {
219 tag: Tag,
220 slice: Slice,
221
222 pub const Slice = struct {
223 start: Index,
224 end: Index,
225
226 pub fn len(self: Slice) Index {
227 return self.end - self.start;
228 }
229
230 pub fn view(self: Slice, bytes: []const u8) []const u8 {
231 return bytes[self.start..self.end];
232 }
233 };
234
235 pub const DecodeError = error{ EndOfStream, InvalidEncoding };
236
237 /// Safely decode a DER/BER/CER element at `index`:
238 /// - Ensures length uses shortest form
239 /// - Ensures length is within `bytes`
240 /// - Ensures length is less than `std.math.maxInt(Index)`
241 pub fn decode(bytes: []const u8, index: Index) DecodeError!Element {
242 if (index > bytes.len) return error.EndOfStream;
243 var reader: std.Io.Reader = .fixed(bytes[index..]);
244
245 const tag = Tag.decode(&reader) catch |err| switch (err) {
246 error.ReadFailed => unreachable, // it's all fixed buffers
247 else => |e| return e,
248 };
249 const size_or_len_size = reader.takeByte() catch |err| switch (err) {
250 error.ReadFailed => unreachable, // it's all fixed buffers
251 else => |e| return e,
252 };
253
254 const len = if (size_or_len_size < 128)
255 // short form between 0-127
256 size_or_len_size
257 else blk: {
258 // long form between 0 and std.math.maxInt(u1024)
259 const len_size: u7 = @truncate(size_or_len_size);
260 if (len_size > @sizeOf(Index)) return error.EndOfStream;
261
262 const len = reader.takeVarInt(Index, .big, len_size) catch |err| switch (err) {
263 error.ReadFailed => unreachable, // it's all fixed buffers
264 else => |e| return e,
265 };
266 if (len < 128) return error.EndOfStream; // should have used short form
267
268 break :blk len;
269 };
270
271 const start = index + @as(Index, @intCast(reader.seek));
272 const end = std.math.add(Index, start, len) catch return error.EndOfStream;
273 if (end > bytes.len) return error.EndOfStream;
274
275 return Element{ .tag = tag, .slice = Slice{ .start = start, .end = end } };
276 }
277};
278
279test Element {
280 const short_form = [_]u8{ 0x30, 0x03, 0x02, 0x01, 0x09 };
281 try std.testing.expectEqual(Element{
282 .tag = Tag.universal(.sequence, true),
283 .slice = Element.Slice{ .start = 2, .end = short_form.len },
284 }, Element.decode(&short_form, 0));
285
286 const long_form = [_]u8{ 0x30, 129, 129 } ++ @as([129]u8, @splat(0));
287 try std.testing.expectEqual(Element{
288 .tag = Tag.universal(.sequence, true),
289 .slice = Element.Slice{ .start = 3, .end = long_form.len },
290 }, Element.decode(&long_form, 0));
291
292 const multi_byte_tag = [_]u8{ 0x1F, 0x20, 0x08, 0x30, 0x36, 0x3A, 0x32, 0x37, 0x3A, 0x31, 0x35 };
293 try std.testing.expectEqual(Element{
294 .tag = Tag.universal(.time_of_day, false),
295 .slice = Element.Slice{ .start = 3, .end = multi_byte_tag.len },
296 }, Element.decode(&multi_byte_tag, 0));
297}
298
299/// For decoding.
300pub const ExpectedTag = struct {
301 number: ?Tag.Number = null,
302 constructed: ?bool = null,
303 class: ?Tag.Class = null,
304
305 pub fn init(number: ?Tag.Number, constructed: ?bool, class: ?Tag.Class) ExpectedTag {
306 return .{ .number = number, .constructed = constructed, .class = class };
307 }
308
309 pub fn primitive(number: ?Tag.Number) ExpectedTag {
310 return .{ .number = number, .constructed = false, .class = .universal };
311 }
312
313 pub fn match(self: ExpectedTag, tag: Tag) bool {
314 if (self.number) |e| {
315 if (tag.number != e) return false;
316 }
317 if (self.constructed) |e| {
318 if (tag.constructed != e) return false;
319 }
320 if (self.class) |e| {
321 if (tag.class != e) return false;
322 }
323 return true;
324 }
325};
326
327pub const FieldTag = struct {
328 number: std.meta.Tag(Tag.Number),
329 class: Tag.Class,
330 explicit: bool = true,
331
332 pub fn initExplicit(number: std.meta.Tag(Tag.Number), class: Tag.Class) FieldTag {
333 return .{ .number = number, .class = class, .explicit = true };
334 }
335
336 pub fn initImplicit(number: std.meta.Tag(Tag.Number), class: Tag.Class) FieldTag {
337 return .{ .number = number, .class = class, .explicit = false };
338 }
339
340 pub fn fromContainer(comptime Container: type, comptime field_name: []const u8) ?FieldTag {
341 if (@hasDecl(Container, "asn1_tags") and @hasField(@TypeOf(Container.asn1_tags), field_name)) {
342 return @field(Container.asn1_tags, field_name);
343 }
344
345 return null;
346 }
347
348 pub fn toTag(self: FieldTag) Tag {
349 return Tag.init(@fromBackingInt(@intCast(self.number)), self.explicit, self.class);
350 }
351};
352
353pub const BitString = struct {
354 /// Number of bits in rightmost byte that are unused.
355 right_padding: u3 = 0,
356 bytes: []const u8,
357
358 pub fn bitLen(self: BitString) usize {
359 return self.bytes.len * 8 - self.right_padding;
360 }
361
362 const asn1_tag = Tag.universal(.bitstring, false);
363
364 pub fn decodeDer(decoder: *der.Decoder) !BitString {
365 const ele = try decoder.element(asn1_tag.toExpected());
366 const bytes = decoder.view(ele);
367
368 if (bytes.len < 1) return error.InvalidBitString;
369 const padding = bytes[0];
370 if (padding >= 8) return error.InvalidBitString;
371 const right_padding: u3 = @intCast(padding);
372
373 // DER requires that unused bits be zero.
374 if (@ctz(bytes[bytes.len - 1]) < right_padding) return error.InvalidBitString;
375
376 return BitString{ .bytes = bytes[1..], .right_padding = right_padding };
377 }
378
379 pub fn encodeDer(self: BitString, encoder: *der.Encoder) !void {
380 try encoder.prependBytes(self.bytes);
381 try encoder.prependBytes(&.{self.right_padding});
382 try encoder.length(self.bytes.len + 1);
383 try encoder.tag(asn1_tag);
384 }
385};
386
387test BitString {
388 const bs = BitString{ .bytes = &.{ 0x6e, 0x5d, 0xc0 }, .right_padding = 6 };
389 const allocator = std.testing.allocator;
390 const buf = try der.encode(allocator, bs);
391 defer allocator.free(buf);
392 try std.testing.expectEqualSlices(u8, &.{ 0x03, 0x04, 0x06, 0x6e, 0x5d, 0xc0 }, buf);
393 try std.testing.expectEqualDeep(bs, try der.decode(BitString, buf));
394}
395
396pub fn Opaque(comptime tag: Tag) type {
397 return struct {
398 bytes: []const u8,
399
400 pub fn decodeDer(decoder: *der.Decoder) !@This() {
401 const ele = try decoder.element(tag.toExpected());
402 if (tag.constructed) decoder.index = ele.slice.end;
403 return .{ .bytes = decoder.view(ele) };
404 }
405
406 pub fn encodeDer(self: @This(), encoder: *der.Encoder) !void {
407 try encoder.tagBytes(tag, self.bytes);
408 }
409 };
410}
411
412/// Use sparingly.
413pub const Any = struct {
414 tag: Tag,
415 bytes: []const u8,
416
417 pub fn decodeDer(decoder: *der.Decoder) !@This() {
418 const ele = try decoder.element(ExpectedTag{});
419 return .{ .tag = ele.tag, .bytes = decoder.view(ele) };
420 }
421
422 pub fn encodeDer(self: @This(), encoder: *der.Encoder) !void {
423 try encoder.tagBytes(self.tag, self.bytes);
424 }
425};
426
427test {
428 _ = der;
429 _ = Oid;
430 _ = @import("asn1/test.zig");
431}