master
  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" => {
 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 (std.meta.fields(T)) |f| {
 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.defaultValue()) |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.defaultValue()) |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 @enumFromInt(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 = @enumFromInt(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    if (@typeInfo(T).int.bits % 8 != 0) @compileError("T must be byte aligned");
115
116    var bytes = value;
117    if (bytes.len >= 2) {
118        if (bytes[0] == 0) {
119            if (@clz(bytes[1]) > 0) return error.NonCanonical;
120            bytes.ptr += 1;
121        }
122        if (bytes[0] == 0xff and @clz(bytes[1]) == 0) return error.NonCanonical;
123    }
124
125    if (bytes.len > @sizeOf(T)) return error.LargeValue;
126    if (@sizeOf(T) == 1) return @bitCast(bytes[0]);
127
128    return std.mem.readVarInt(T, bytes, .big);
129}
130
131test int {
132    try expectEqual(@as(u8, 1), try int(u8, &[_]u8{1}));
133    try expectError(error.NonCanonical, int(u8, &[_]u8{ 0, 1 }));
134    try expectError(error.NonCanonical, int(u8, &[_]u8{ 0xff, 0xff }));
135
136    const big = [_]u8{ 0xef, 0xff };
137    try expectError(error.LargeValue, int(u8, &big));
138    try expectEqual(0xefff, int(u16, &big));
139}
140
141test Decoder {
142    var parser = Decoder{ .bytes = @embedFile("./testdata/id_ecc.pub.der") };
143    const seq = try parser.sequence();
144
145    {
146        const seq2 = try parser.sequence();
147        _ = try parser.element(ExpectedTag.init(.oid, false, .universal));
148        _ = try parser.element(ExpectedTag.init(.oid, false, .universal));
149
150        try std.testing.expectEqual(parser.index, seq2.slice.end);
151    }
152    _ = try parser.element(ExpectedTag.init(.bitstring, false, .universal));
153
154    try std.testing.expectEqual(parser.index, seq.slice.end);
155    try std.testing.expectEqual(parser.index, parser.bytes.len);
156}
157
158const std = @import("std");
159const builtin = @import("builtin");
160const asn1 = @import("../../asn1.zig");
161const Oid = @import("../Oid.zig");
162
163const expectEqual = std.testing.expectEqual;
164const expectError = std.testing.expectError;
165const Decoder = @This();
166const Index = asn1.Index;
167const Tag = asn1.Tag;
168const FieldTag = asn1.FieldTag;
169const ExpectedTag = asn1.ExpectedTag;
170const Element = asn1.Element;