| 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. |
| 10 | bytes: []const u8, |
| 11 | index: 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`. |
| 14 | field_tag: ?FieldTag = null, |
| 15 | |
| 16 | /// Expect a value. |
| 17 | pub 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. |
| 83 | pub fn sequence(self: *Decoder) !Element { |
| 84 | return try self.element(ExpectedTag.init(.sequence, true, .universal)); |
| 85 | } |
| 86 | |
| 87 | //// Expect an element. |
| 88 | pub 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. |
| 109 | pub fn view(self: Decoder, elem: Element) []const u8 { |
| 110 | return elem.slice.view(self.bytes); |
| 111 | } |
| 112 | |
| 113 | fn 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 | |
| 143 | test 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 | |
| 172 | test 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 | |
| 189 | const std = @import("std"); |
| 190 | const builtin = @import("builtin"); |
| 191 | const asn1 = @import("../../asn1.zig"); |
| 192 | const Oid = @import("../Oid.zig"); |
| 193 | |
| 194 | const expectEqual = std.testing.expectEqual; |
| 195 | const expectError = std.testing.expectError; |
| 196 | const Decoder = @This(); |
| 197 | const Index = asn1.Index; |
| 198 | const Tag = asn1.Tag; |
| 199 | const FieldTag = asn1.FieldTag; |
| 200 | const ExpectedTag = asn1.ExpectedTag; |
| 201 | const Element = asn1.Element; |