1//! A secure DER parser that:
2//! - Prefers calling `fn decodeDer(self: @This(), decoder: *der.Decoder)`
3//! - Does NOT allocate. If you wish to parse lists you can do so lazily
4//! with an opaque type.
5//! - Does NOT read memory outside `bytes`.
6//! - Does NOT return elements with slices outside `bytes`.
7//! - Errors on values that do NOT follow DER rules:
8//! - Lengths that could be represented in a shorter form.
9//! - Booleans that are not 0xff or 0x00.
10bytes: []const u8,
11index: Index = 0,
12/// The field tag of the most recently visited field.
13/// This is needed because we might visit an implicitly tagged container with a `fn decodeDer`.
14field_tag: ?FieldTag = null,
15
16/// Expect a value.
17pub fn any(self: *Decoder, comptime T: type) !T {
18 if (std.meta.hasFn(T, "decodeDer")) return try T.decodeDer(self);
19
20 const tag = Tag.fromZig(T).toExpected();
21 switch (@typeInfo(T)) {
22 .@"struct" => |info| {
23 const ele = try self.element(tag);
24 defer self.index = ele.slice.end; // don't force parsing all fields
25
26 var res: T = undefined;
27
28 inline for (info.field_names, info.field_types, info.field_attrs) |f_name, f_type, f_attrs| {
29 self.field_tag = FieldTag.fromContainer(T, f_name);
30
31 if (self.field_tag) |ft| {
32 if (ft.explicit) {
33 const seq = try self.element(ft.toTag().toExpected());
34 self.index = seq.slice.start;
35 self.field_tag = null;
36 }
37 }
38
39 @field(res, f_name) = self.any(f_type) catch |err| brk: {
40 if (f_attrs.defaultValue(f_type)) |d| {
41 break :brk d;
42 }
43 return err;
44 };
45 // DER encodes null values by skipping them.
46 if (@typeInfo(f_type) == .optional and @field(res, f_name) == null) {
47 if (f_attrs.defaultValue(f_type)) |d| @field(res, f_name) = d;
48 }
49 }
50
51 return res;
52 },
53 .bool => {
54 const ele = try self.element(tag);
55 const bytes = self.view(ele);
56 if (bytes.len != 1) return error.InvalidBool;
57
58 return switch (bytes[0]) {
59 0x00 => false,
60 0xff => true,
61 else => error.InvalidBool,
62 };
63 },
64 .int => {
65 const ele = try self.element(tag);
66 const bytes = self.view(ele);
67 return try int(T, bytes);
68 },
69 .@"enum" => |e| {
70 const ele = try self.element(tag);
71 const bytes = self.view(ele);
72 if (@hasDecl(T, "oids")) {
73 return T.oids.oidToEnum(bytes) orelse return error.UnknownOid;
74 }
75 return @fromBackingInt(@intCast(try int(e.tag_type, bytes)));
76 },
77 .optional => |o| return self.any(o.child) catch return null,
78 else => @compileError("cannot decode type " ++ @typeName(T)),
79 }
80}
81
82//// Expect a sequence.
83pub fn sequence(self: *Decoder) !Element {
84 return try self.element(ExpectedTag.init(.sequence, true, .universal));
85}
86
87//// Expect an element.
88pub fn element(
89 self: *Decoder,
90 expected: ExpectedTag,
91) (error{ EndOfStream, UnexpectedElement } || Element.DecodeError)!Element {
92 if (self.index >= self.bytes.len) return error.EndOfStream;
93
94 const res = try Element.decode(self.bytes, self.index);
95 var e = expected;
96 if (self.field_tag) |ft| {
97 e.number = @fromBackingInt(@intCast(ft.number));
98 e.class = ft.class;
99 }
100 if (!e.match(res.tag)) {
101 return error.UnexpectedElement;
102 }
103
104 self.index = if (res.tag.constructed) res.slice.start else res.slice.end;
105 return res;
106}
107
108/// View of element bytes.
109pub fn view(self: Decoder, elem: Element) []const u8 {
110 return elem.slice.view(self.bytes);
111}
112
113fn int(comptime T: type, value: []const u8) error{ NonCanonical, LargeValue }!T {
114 const info = @typeInfo(T).int;
115 if (info.bits % 8 != 0) @compileError("T must be byte aligned");
116
117 if (value.len == 0) return error.NonCanonical;
118 if (value.len >= 2) {
119 if (value[0] == 0x00 and value[1] & 0x80 == 0) return error.NonCanonical;
120 if (value[0] == 0xff and value[1] & 0x80 != 0) return error.NonCanonical;
121 }
122
123 const had_sign_byte = value.len >= 2 and value[0] == 0x00;
124 const bytes = if (had_sign_byte) value[1..] else value;
125 const der_negative = !had_sign_byte and bytes[0] & 0x80 != 0;
126
127 switch (info.signedness) {
128 .unsigned => {
129 if (der_negative) return error.LargeValue;
130 if (bytes.len > @sizeOf(T)) return error.LargeValue;
131 },
132 .signed => {
133 const max_len: usize = if (had_sign_byte) @sizeOf(T) - 1 else @sizeOf(T);
134 if (bytes.len > max_len) return error.LargeValue;
135 },
136 }
137
138 var buf: [@sizeOf(T)]u8 = @splat(if (der_negative) 0xff else 0);
139 @memcpy(buf[buf.len - bytes.len ..], bytes);
140 return std.mem.readInt(T, &buf, .big);
141}
142
143test int {
144 try expectEqual(@as(u8, 1), try int(u8, &[_]u8{1}));
145 try expectError(error.NonCanonical, int(u8, &[_]u8{ 0, 1 }));
146 try expectError(error.NonCanonical, int(u8, &[_]u8{ 0xff, 0xff }));
147
148 const big = [_]u8{ 0xef, 0xff };
149 try expectError(error.LargeValue, int(u8, &big));
150 try expectError(error.LargeValue, int(u16, &big));
151 try expectEqual(@as(i16, -4097), try int(i16, &big));
152
153 try expectEqual(@as(u16, 255), try int(u16, &.{ 0x00, 0xff }));
154 try expectEqual(@as(u16, 0x8000), try int(u16, &.{ 0x00, 0x80, 0x00 }));
155
156 try expectEqual(@as(i8, -1), try int(i8, &.{0xff}));
157 try expectEqual(@as(i16, -1), try int(i16, &.{0xff}));
158 try expectEqual(@as(i16, -128), try int(i16, &.{0x80}));
159 try expectEqual(@as(i16, -129), try int(i16, &.{ 0xff, 0x7f }));
160 try expectEqual(@as(i16, 255), try int(i16, &.{ 0x00, 0xff }));
161 try expectEqual(@as(i32, 0x7fffffff), try int(i32, &.{ 0x7f, 0xff, 0xff, 0xff }));
162
163 try expectError(error.LargeValue, int(i8, &.{ 0x00, 0xff }));
164 try expectError(error.LargeValue, int(i16, &.{ 0x00, 0x80, 0x00 }));
165 try expectError(error.LargeValue, int(i32, &.{ 0x00, 0x80, 0x00, 0x00, 0x00 }));
166
167 try expectError(error.LargeValue, int(u8, &.{0xff}));
168 try expectError(error.LargeValue, int(u16, &.{0x80}));
169 try expectError(error.LargeValue, int(u32, &.{ 0x80, 0x00, 0x00, 0x00 }));
170}
171
172test Decoder {
173 var parser = Decoder{ .bytes = @embedFile("./testdata/id_ecc.pub.der") };
174 const seq = try parser.sequence();
175
176 {
177 const seq2 = try parser.sequence();
178 _ = try parser.element(ExpectedTag.init(.oid, false, .universal));
179 _ = try parser.element(ExpectedTag.init(.oid, false, .universal));
180
181 try std.testing.expectEqual(parser.index, seq2.slice.end);
182 }
183 _ = try parser.element(ExpectedTag.init(.bitstring, false, .universal));
184
185 try std.testing.expectEqual(parser.index, seq.slice.end);
186 try std.testing.expectEqual(parser.index, parser.bytes.len);
187}
188
189const std = @import("std");
190const builtin = @import("builtin");
191const asn1 = @import("../../asn1.zig");
192const Oid = @import("../Oid.zig");
193
194const expectEqual = std.testing.expectEqual;
195const expectError = std.testing.expectError;
196const Decoder = @This();
197const Index = asn1.Index;
198const Tag = asn1.Tag;
199const FieldTag = asn1.FieldTag;
200const ExpectedTag = asn1.ExpectedTag;
201const Element = asn1.Element;