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}