authorgravatar for clickingbuttons@pm.meclickingbuttons <clickingbuttons@pm.me> 2024-05-15 13:54:20-04:00
committergravatar for clickingbuttons@pm.meclickingbuttons <clickingbuttons@pm.me> 2024-05-15 15:59:24-04:00
log330d353d6e09ac1d48dedd1bfc127f81021b4b1f
tree16b6287690c4084d3a3a23433c10289a5a6e7f85
parent6a65561e3e5f82f126ec4795e5cd9c07392b457b

std.crypto: Add ASN1 module with OIDs and DER

Add module for mapping ASN1 types to Zig types. See `asn1.Tag.fromZig` for the mapping. Add DER encoder and decoder. See `asn1/test.zig` for example usage of every ASN1 type. This implementation allows ASN1 tags to be overriden with `asn1_tag` and `asn1_tags`: ```zig const MyContainer = (enum | union | struct) { field: u32, pub const asn1_tag = asn1.Tag.init(...); // This specifies a tag's class, and if explicit, additional encoding // rules. pub const asn1_tags = .{ .field = asn1.FieldTag.explicit(0, .context_specific), }; }; ``` Despite having an enum tag type, ASN1 frequently uses OIDs as enum values. This is supported via an `pub const oids` field. ```zig const MyEnum = enum { a, pub const oids = asn1.Oid.StaticMap(MyEnum).initComptime(.{ .a = "1.2.3.4", }); }; ``` Futhermore, a container may choose to implement encoding and decoding however it deems fit. This allows for derived fields since Zig has a far more powerful type system than ASN1. ```zig // ASN1 has no standard way of tagging unions. const MyContainer = union(enum) { derived: PowerfulZigType, const WeakAsn1Type = ...; pub fn encodeDer(self: MyContainer, encoder: *der.Encoder) !void { try encoder.any(WeakAsn1Type{...}); } pub fn decodeDer(decoder: *der.Decoder) !MyContainer { const weak_asn1_type = try decoder.any(WeakAsn1Type); return .{ .derived = PowerfulZigType{...} }; } }; ``` An unfortunate side-effect is that decoding and encoding cannot have complete complete error sets unless we limit what errors users may return. Luckily, PKI ASN1 types are NOT recursive so the inferred error set should be sufficient. Finally, other encodings are possible, but this patch only implements a buffered DER encoder and decoder. In an effort to keep the changeset minimal this PR does not actually use the DER parser for stdlib PKI, but a tested example of how it may be used for Certificate is available [here.](https://github.com/clickingbuttons/asn1/blob/69c5709d/src/Certificate.zig) Closes #19775.

10 files changed, 1097 insertions(+), 0 deletions(-)

lib/std/crypto.zig+2
......@@ -194,6 +194,7 @@ pub const errors = @import("crypto/errors.zig");
194194
195195pub const tls = @import("crypto/tls.zig");
196196pub const Certificate = @import("crypto/Certificate.zig");
197pub const asn1 = @import("crypto/asn1.zig");
197198
198199/// Side-channels mitigations.
199200pub const SideChannelsMitigations = enum {
......@@ -307,6 +308,7 @@ test {
307308 _ = errors;
308309 _ = tls;
309310 _ = Certificate;
311 _ = asn1;
310312}
311313
312314test "CSPRNG" {
lib/std/crypto/asn1.zig created+359
......@@ -0,0 +1,359 @@
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: anytype) !Tag {
73 const tag1: FirstTag = @bitCast(try reader.readByte());
74 var number: u14 = tag1.number;
75
76 if (tag1.number == 15) {
77 const tag2: NextTag = @bitCast(try reader.readByte());
78 number = tag2.number;
79 if (tag2.continues) {
80 const tag3: NextTag = @bitCast(try reader.readByte());
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: anytype) @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 stream = std.io.fixedBufferStream(&buffer);
102 var writer2 = stream.writer();
103
104 switch (@intFromEnum(self.number)) {
105 0...std.math.maxInt(u5) => |n| {
106 tag1.number = @intCast(n);
107 writer2.writeByte(@bitCast(tag1)) catch unreachable;
108 },
109 std.math.maxInt(u5) + 1...std.math.maxInt(u7) => |n| {
110 tag1.number = 15;
111 const tag2 = NextTag{ .number = @intCast(n), .continues = false };
112 writer2.writeByte(@bitCast(tag1)) catch unreachable;
113 writer2.writeByte(@bitCast(tag2)) catch unreachable;
114 },
115 else => |n| {
116 tag1.number = 15;
117 const tag2 = NextTag{ .number = @intCast(n >> 7), .continues = true };
118 const tag3 = NextTag{ .number = @truncate(n), .continues = false };
119 writer2.writeByte(@bitCast(tag1)) catch unreachable;
120 writer2.writeByte(@bitCast(tag2)) catch unreachable;
121 writer2.writeByte(@bitCast(tag3)) catch unreachable;
122 },
123 }
124
125 _ = try writer.write(stream.getWritten());
126 }
127
128 const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class };
129 const NextTag = packed struct(u8) { number: u7, continues: bool };
130
131 pub fn toExpected(self: Tag) ExpectedTag {
132 return ExpectedTag{
133 .number = self.number,
134 .constructed = self.constructed,
135 .class = self.class,
136 };
137 }
138
139 pub fn fromZig(comptime T: type) Tag {
140 switch (@typeInfo(T)) {
141 .Struct, .Enum, .Union => {
142 if (@hasDecl(T, "asn1_tag")) return T.asn1_tag;
143 },
144 else => {},
145 }
146
147 switch (@typeInfo(T)) {
148 .Struct, .Union => return universal(.sequence, true),
149 .Bool => return universal(.boolean, false),
150 .Int => return universal(.integer, false),
151 .Enum => |e| {
152 if (@hasDecl(T, "oids")) return Oid.asn1_tag;
153 return universal(if (e.is_exhaustive) .enumerated else .integer, false);
154 },
155 .Optional => |o| return fromZig(o.child),
156 .Null => return universal(.null, false),
157 else => @compileError("cannot map Zig type to asn1_tag " ++ @typeName(T)),
158 }
159 }
160};
161
162test Tag {
163 const buf = [_]u8{0xa3};
164 var stream = std.io.fixedBufferStream(&buf);
165 const t = Tag.decode(stream.reader());
166 try std.testing.expectEqual(Tag.init(@enumFromInt(3), true, .context_specific), t);
167}
168
169/// A decoded view.
170pub const Element = struct {
171 tag: Tag,
172 slice: Slice,
173
174 pub const Slice = struct {
175 start: Index,
176 end: Index,
177
178 pub fn len(self: Slice) Index {
179 return self.end - self.start;
180 }
181
182 pub fn view(self: Slice, bytes: []const u8) []const u8 {
183 return bytes[self.start..self.end];
184 }
185 };
186
187 pub const DecodeError = error{ InvalidLength, EndOfStream };
188
189 /// Safely decode a DER/BER/CER element at `index`:
190 /// - Ensures length uses shortest form
191 /// - Ensures length is within `bytes`
192 /// - Ensures length is less than `std.math.maxInt(Index)`
193 pub fn decode(bytes: []const u8, index: Index) DecodeError!Element {
194 var stream = std.io.fixedBufferStream(bytes[index..]);
195 var reader = stream.reader();
196
197 const tag = try Tag.decode(reader);
198 const size_or_len_size = try reader.readByte();
199
200 var start = index + 2;
201 var end = start + size_or_len_size;
202 // short form between 0-127
203 if (size_or_len_size < 128) {
204 if (end > bytes.len) return error.InvalidLength;
205 } else {
206 // long form between 0 and std.math.maxInt(u1024)
207 const len_size: u7 = @truncate(size_or_len_size);
208 start += len_size;
209 if (len_size > @sizeOf(Index)) return error.InvalidLength;
210
211 const len = try reader.readVarInt(Index, .big, len_size);
212 if (len < 128) return error.InvalidLength; // should have used short form
213
214 end = std.math.add(Index, start, len) catch return error.InvalidLength;
215 if (end > bytes.len) return error.InvalidLength;
216 }
217
218 return Element{ .tag = tag, .slice = Slice{ .start = start, .end = end } };
219 }
220};
221
222test Element {
223 const short_form = [_]u8{ 0x30, 0x03, 0x02, 0x01, 0x09 };
224 try std.testing.expectEqual(Element{
225 .tag = Tag.universal(.sequence, true),
226 .slice = Element.Slice{ .start = 2, .end = short_form.len },
227 }, Element.decode(&short_form, 0));
228
229 const long_form = [_]u8{ 0x30, 129, 129 } ++ [_]u8{0} ** 129;
230 try std.testing.expectEqual(Element{
231 .tag = Tag.universal(.sequence, true),
232 .slice = Element.Slice{ .start = 3, .end = long_form.len },
233 }, Element.decode(&long_form, 0));
234}
235
236/// For decoding.
237pub const ExpectedTag = struct {
238 number: ?Tag.Number = null,
239 constructed: ?bool = null,
240 class: ?Tag.Class = null,
241
242 pub fn init(number: ?Tag.Number, constructed: ?bool, class: ?Tag.Class) ExpectedTag {
243 return .{ .number = number, .constructed = constructed, .class = class };
244 }
245
246 pub fn primitive(number: ?Tag.Number) ExpectedTag {
247 return .{ .number = number, .constructed = false, .class = .universal };
248 }
249
250 pub fn match(self: ExpectedTag, tag: Tag) bool {
251 if (self.number) |e| {
252 if (tag.number != e) return false;
253 }
254 if (self.constructed) |e| {
255 if (tag.constructed != e) return false;
256 }
257 if (self.class) |e| {
258 if (tag.class != e) return false;
259 }
260 return true;
261 }
262};
263
264pub const FieldTag = struct {
265 number: std.meta.Tag(Tag.Number),
266 class: Tag.Class,
267 explicit: bool = true,
268
269 pub fn explicit(number: std.meta.Tag(Tag.Number), class: Tag.Class) FieldTag {
270 return FieldTag{ .number = number, .class = class, .explicit = true };
271 }
272
273 pub fn implicit(number: std.meta.Tag(Tag.Number), class: Tag.Class) FieldTag {
274 return FieldTag{ .number = number, .class = class, .explicit = false };
275 }
276
277 pub fn fromContainer(comptime Container: type, comptime field_name: []const u8) ?FieldTag {
278 if (@hasDecl(Container, "asn1_tags") and @hasField(@TypeOf(Container.asn1_tags), field_name)) {
279 return @field(Container.asn1_tags, field_name);
280 }
281
282 return null;
283 }
284
285 pub fn toTag(self: FieldTag) Tag {
286 return Tag.init(@enumFromInt(self.number), self.explicit, self.class);
287 }
288};
289
290pub const BitString = struct {
291 /// Number of bits in rightmost byte that are unused.
292 right_padding: u3 = 0,
293 bytes: []const u8,
294
295 pub fn bitLen(self: BitString) usize {
296 return self.bytes.len * 8 - self.right_padding;
297 }
298
299 const asn1_tag = Tag.universal(.bitstring, false);
300
301 pub fn decodeDer(decoder: *der.Decoder) !BitString {
302 const ele = try decoder.element(asn1_tag.toExpected());
303 const bytes = decoder.view(ele);
304
305 if (bytes.len < 1) return error.InvalidBitString;
306 const padding = bytes[0];
307 if (padding >= 8) return error.InvalidBitString;
308 const right_padding: u3 = @intCast(padding);
309
310 // DER requires that unused bits be zero.
311 if (@ctz(bytes[bytes.len - 1]) < right_padding) return error.InvalidBitString;
312
313 return BitString{ .bytes = bytes[1..], .right_padding = right_padding };
314 }
315
316 pub fn encodeDer(self: BitString, encoder: *der.Encoder) !void {
317 try encoder.writer().writeAll(self.bytes);
318 try encoder.writer().writeByte(self.right_padding);
319 try encoder.length(self.bytes.len + 1);
320 try encoder.tag(asn1_tag);
321 }
322};
323
324pub fn Opaque(comptime tag: Tag) type {
325 return struct {
326 bytes: []const u8,
327
328 pub fn decodeDer(decoder: *der.Decoder) !@This() {
329 const ele = try decoder.element(tag.toExpected());
330 if (tag.constructed) decoder.index = ele.slice.end;
331 return .{ .bytes = decoder.view(ele) };
332 }
333
334 pub fn encodeDer(self: @This(), encoder: *der.Encoder) !void {
335 try encoder.tagBytes(tag, self.bytes);
336 }
337 };
338}
339
340/// Use sparingly.
341pub const Any = struct {
342 tag: Tag,
343 bytes: []const u8,
344
345 pub fn decodeDer(decoder: *der.Decoder) !@This() {
346 const ele = try decoder.element(ExpectedTag{});
347 return .{ .tag = ele.tag, .bytes = decoder.view(ele) };
348 }
349
350 pub fn encodeDer(self: @This(), encoder: *der.Encoder) !void {
351 try encoder.tagBytes(self.tag, self.bytes);
352 }
353};
354
355test {
356 _ = der;
357 _ = Oid;
358 _ = @import("asn1/test.zig");
359}
lib/std/crypto/asn1/Oid.zig created+204
......@@ -0,0 +1,204 @@
1//! Globally unique hierarchical identifier made of a sequence of integers.
2//!
3//! Commonly used to identify standards, algorithms, certificate extensions,
4//! organizations, or policy documents.
5encoded: []const u8,
6
7pub const InitError = std.fmt.ParseIntError || error{MissingPrefix} || std.io.FixedBufferStream(u8).WriteError;
8
9pub fn fromDot(dot_notation: []const u8, out: []u8) InitError!Oid {
10 var split = std.mem.splitScalar(u8, dot_notation, '.');
11 const first_str = split.next() orelse return error.MissingPrefix;
12 const second_str = split.next() orelse return error.MissingPrefix;
13
14 const first = try std.fmt.parseInt(u8, first_str, 10);
15 const second = try std.fmt.parseInt(u8, second_str, 10);
16
17 var stream = std.io.fixedBufferStream(out);
18 var writer = stream.writer();
19
20 try writer.writeByte(first * 40 + second);
21
22 var i: usize = 1;
23 while (split.next()) |s| {
24 var parsed = try std.fmt.parseUnsigned(Arc, s, 10);
25 const n_bytes = if (parsed == 0) 0 else std.math.log(Arc, encoding_base, parsed);
26
27 for (0..n_bytes) |j| {
28 const place = std.math.pow(Arc, encoding_base, n_bytes - @as(Arc, @intCast(j)));
29 const digit: u8 = @intCast(@divFloor(parsed, place));
30
31 try writer.writeByte(digit | 0x80);
32 parsed -= digit * place;
33
34 i += 1;
35 }
36 try writer.writeByte(@intCast(parsed));
37 i += 1;
38 }
39
40 return .{ .encoded = stream.getWritten() };
41}
42
43test fromDot {
44 var buf: [256]u8 = undefined;
45 for (test_cases) |t| {
46 const actual = try fromDot(t.dot_notation, &buf);
47 try std.testing.expectEqualSlices(u8, t.encoded, actual.encoded);
48 }
49}
50
51pub fn toDot(self: Oid, writer: anytype) @TypeOf(writer).Error!void {
52 const encoded = self.encoded;
53 const first = @divTrunc(encoded[0], 40);
54 const second = encoded[0] - first * 40;
55 try writer.print("{d}.{d}", .{ first, second });
56
57 var i: usize = 1;
58 while (i != encoded.len) {
59 const n_bytes: usize = brk: {
60 var res: usize = 1;
61 var j: usize = i;
62 while (encoded[j] & 0x80 != 0) {
63 res += 1;
64 j += 1;
65 }
66 break :brk res;
67 };
68
69 var n: usize = 0;
70 for (0..n_bytes) |j| {
71 const place = std.math.pow(usize, encoding_base, n_bytes - j - 1);
72 n += place * (encoded[i] & 0b01111111);
73 i += 1;
74 }
75 try writer.print(".{d}", .{n});
76 }
77}
78
79test toDot {
80 var buf: [256]u8 = undefined;
81
82 for (test_cases) |t| {
83 var stream = std.io.fixedBufferStream(&buf);
84 try toDot(Oid{ .encoded = t.encoded }, stream.writer());
85 try std.testing.expectEqualStrings(t.dot_notation, stream.getWritten());
86 }
87}
88
89const TestCase = struct {
90 encoded: []const u8,
91 dot_notation: []const u8,
92
93 pub fn init(comptime hex: []const u8, dot_notation: []const u8) TestCase {
94 return .{ .encoded = &hexToBytes(hex), .dot_notation = dot_notation };
95 }
96};
97
98const test_cases = [_]TestCase{
99 // https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier
100 TestCase.init("2b0601040182371514", "1.3.6.1.4.1.311.21.20"),
101 // https://luca.ntop.org/Teaching/Appunti/asn1.html
102 TestCase.init("2a864886f70d", "1.2.840.113549"),
103 // https://www.sysadmins.lv/blog-en/how-to-encode-object-identifier-to-an-asn1-der-encoded-string.aspx
104 TestCase.init("2a868d20", "1.2.100000"),
105 TestCase.init("2a864886f70d01010b", "1.2.840.113549.1.1.11"),
106 TestCase.init("2b6570", "1.3.101.112"),
107};
108
109pub const asn1_tag = asn1.Tag.init(.oid, false, .universal);
110
111pub fn decodeDer(decoder: *der.Decoder) !Oid {
112 const ele = try decoder.element(asn1_tag.toExpected());
113 return Oid{ .encoded = decoder.view(ele) };
114}
115
116pub fn encodeDer(self: Oid, encoder: *der.Encoder) !void {
117 try encoder.tagBytes(asn1_tag, self.encoded);
118}
119
120fn encodedLen(dot_notation: []const u8) usize {
121 var buf: [256]u8 = undefined;
122 const oid = fromDot(dot_notation, &buf) catch unreachable;
123 return oid.encoded.len;
124}
125
126pub fn encodeComptime(comptime dot_notation: []const u8) [encodedLen(dot_notation)]u8 {
127 @setEvalBranchQuota(4000);
128 comptime var buf: [256]u8 = undefined;
129 const oid = comptime fromDot(dot_notation, &buf) catch unreachable;
130 return oid.encoded[0..oid.encoded.len].*;
131}
132
133test encodeComptime {
134 try std.testing.expectEqual(
135 hexToBytes("2b0601040182371514"),
136 comptime encodeComptime("1.3.6.1.4.1.311.21.20"),
137 );
138}
139
140/// Maps of:
141/// - Oid -> enum
142/// - Enum -> oid
143pub fn StaticMap(comptime Enum: type) type {
144 const enum_info = @typeInfo(Enum).Enum;
145 const EnumToOid = std.EnumArray(Enum, []const u8);
146 const ReturnType = struct {
147 oid_to_enum: std.StaticStringMap(Enum),
148 enum_to_oid: EnumToOid,
149
150 pub fn oidToEnum(self: @This(), encoded: []const u8) ?Enum {
151 return self.oid_to_enum.get(encoded);
152 }
153
154 pub fn enumToOid(self: @This(), value: Enum) Oid {
155 const bytes = self.enum_to_oid.get(value);
156 return .{ .encoded = bytes };
157 }
158 };
159
160 return struct {
161 pub fn initComptime(comptime key_pairs: anytype) ReturnType {
162 const struct_info = @typeInfo(@TypeOf(key_pairs)).Struct;
163 const error_msg = "Each field of '" ++ @typeName(Enum) ++ "' must map to exactly one OID";
164 if (!enum_info.is_exhaustive or enum_info.fields.len != struct_info.fields.len) {
165 @compileError(error_msg);
166 }
167
168 comptime var enum_to_oid = EnumToOid.initUndefined();
169
170 const KeyPair = struct { []const u8, Enum };
171 comptime var static_key_pairs: [enum_info.fields.len]KeyPair = undefined;
172
173 comptime for (enum_info.fields, 0..) |f, i| {
174 if (!@hasField(@TypeOf(key_pairs), f.name)) {
175 @compileError("Field '" ++ f.name ++ "' missing Oid.StaticMap entry");
176 }
177 const encoded = &encodeComptime(@field(key_pairs, f.name));
178 const tag: Enum = @enumFromInt(f.value);
179 static_key_pairs[i] = .{ encoded, tag };
180 enum_to_oid.set(tag, encoded);
181 };
182
183 const oid_to_enum = std.StaticStringMap(Enum).initComptime(static_key_pairs);
184 if (oid_to_enum.values().len != enum_info.fields.len) @compileError(error_msg);
185
186 return ReturnType{ .oid_to_enum = oid_to_enum, .enum_to_oid = enum_to_oid };
187 }
188 };
189}
190
191/// Strictly for testing.
192fn hexToBytes(comptime hex: []const u8) [hex.len / 2]u8 {
193 var res: [hex.len / 2]u8 = undefined;
194 _ = std.fmt.hexToBytes(&res, hex) catch unreachable;
195 return res;
196}
197
198const std = @import("std");
199const Oid = @This();
200const Arc = u32;
201const encoding_base = 128;
202const Allocator = std.mem.Allocator;
203const der = @import("der.zig");
204const asn1 = @import("../asn1.zig");
lib/std/crypto/asn1/der.zig created+29
......@@ -0,0 +1,29 @@
1//! Distinguised Encoding Rules as defined in X.690 and X.691.
2//!
3//! Subset of Basic Encoding Rules (BER) which eliminates flexibility in
4//! an effort to acheive normality. Used in PKI.
5const std = @import("std");
6const asn1 = @import("../asn1.zig");
7
8pub const Decoder = @import("der/Decoder.zig");
9pub const Encoder = @import("der/Encoder.zig");
10
11pub fn decode(comptime T: type, encoded: []const u8) !T {
12 var decoder = Decoder{ .bytes = encoded };
13 const res = try decoder.any(T);
14 std.debug.assert(decoder.index == encoded.len);
15 return res;
16}
17
18/// Caller owns returned memory.
19pub fn encode(allocator: std.mem.Allocator, value: anytype) ![]u8 {
20 var encoder = Encoder.init(allocator);
21 defer encoder.deinit();
22 try encoder.any(value);
23 return try encoder.buffer.toOwnedSlice();
24}
25
26test {
27 _ = Decoder;
28 _ = Encoder;
29}
lib/std/crypto/asn1/der/ArrayListReverse.zig created+97
......@@ -0,0 +1,97 @@
1//! An ArrayList that grows backwards. Counts nested prefix length fields
2//! in O(n) instead of O(n^depth) at the cost of extra buffering.
3//!
4//! Laid out in memory like:
5//! capacity |--------------------------|
6//! data |-------------|
7data: []u8,
8capacity: usize,
9allocator: Allocator,
10
11const ArrayListReverse = @This();
12const Error = Allocator.Error;
13
14pub fn init(allocator: Allocator) ArrayListReverse {
15 return .{ .data = &.{}, .capacity = 0, .allocator = allocator };
16}
17
18pub fn deinit(self: *ArrayListReverse) void {
19 self.allocator.free(self.allocatedSlice());
20}
21
22pub fn ensureCapacity(self: *ArrayListReverse, new_capacity: usize) Error!void {
23 if (self.capacity >= new_capacity) return;
24
25 const old_memory = self.allocatedSlice();
26 // Just make a new allocation to not worry about aliasing.
27 const new_memory = try self.allocator.alloc(u8, new_capacity);
28 @memcpy(new_memory[new_capacity - self.data.len ..], self.data);
29 self.allocator.free(old_memory);
30 self.data.ptr = new_memory.ptr + new_capacity - self.data.len;
31 self.capacity = new_memory.len;
32}
33
34pub fn prependSlice(self: *ArrayListReverse, data: []const u8) Error!void {
35 try self.ensureCapacity(self.data.len + data.len);
36 const old_len = self.data.len;
37 const new_len = old_len + data.len;
38 assert(new_len <= self.capacity);
39 self.data.len = new_len;
40
41 const end = self.data.ptr;
42 const begin = end - data.len;
43 const slice = begin[0..data.len];
44 @memcpy(slice, data);
45 self.data.ptr = begin;
46}
47
48pub const Writer = std.io.Writer(*ArrayListReverse, Error, prependSliceSize);
49/// Warning: This writer writes backwards. `fn print` will NOT work as expected.
50pub fn writer(self: *ArrayListReverse) Writer {
51 return .{ .context = self };
52}
53
54fn prependSliceSize(self: *ArrayListReverse, data: []const u8) Error!usize {
55 try self.prependSlice(data);
56 return data.len;
57}
58
59fn allocatedSlice(self: *ArrayListReverse) []u8 {
60 return (self.data.ptr + self.data.len - self.capacity)[0..self.capacity];
61}
62
63/// Invalidates all element pointers.
64pub fn clearAndFree(self: *ArrayListReverse) void {
65 self.allocator.free(self.allocatedSlice());
66 self.data.len = 0;
67 self.capacity = 0;
68}
69
70/// The caller owns the returned memory.
71/// Capacity is cleared, making deinit() safe but unnecessary to call.
72pub fn toOwnedSlice(self: *ArrayListReverse) Error![]u8 {
73 const new_memory = try self.allocator.alloc(u8, self.data.len);
74 @memcpy(new_memory, self.data);
75 @memset(self.data, undefined);
76 self.clearAndFree();
77 return new_memory;
78}
79
80const std = @import("std");
81const Allocator = std.mem.Allocator;
82const assert = std.debug.assert;
83const testing = std.testing;
84
85test ArrayListReverse {
86 var b = ArrayListReverse.init(testing.allocator);
87 defer b.deinit();
88 const data: []const u8 = &.{ 4, 5, 6 };
89 try b.prependSlice(data);
90 try testing.expectEqual(data.len, b.data.len);
91 try testing.expectEqualSlices(u8, data, b.data);
92
93 const data2: []const u8 = &.{ 1, 2, 3 };
94 try b.prependSlice(data2);
95 try testing.expectEqual(data.len + data2.len, b.data.len);
96 try testing.expectEqualSlices(u8, data2 ++ data, b.data);
97}
lib/std/crypto/asn1/der/Decoder.zig created+165
......@@ -0,0 +1,165 @@
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
16pub fn any(self: *Decoder, comptime T: type) !T {
17 if (std.meta.hasFn(T, "decodeDer")) return try T.decodeDer(self);
18
19 const tag = Tag.fromZig(T).toExpected();
20 switch (@typeInfo(T)) {
21 .Struct => {
22 const ele = try self.element(tag);
23 defer self.index = ele.slice.end; // don't force parsing all fields
24
25 var res: T = undefined;
26
27 inline for (std.meta.fields(T)) |f| {
28 self.field_tag = FieldTag.fromContainer(T, f.name);
29
30 if (self.field_tag) |ft| {
31 if (ft.explicit) {
32 const seq = try self.element(ft.toTag().toExpected());
33 self.index = seq.slice.start;
34 self.field_tag = null;
35 }
36 }
37
38 @field(res, f.name) = self.any(f.type) catch |err| brk: {
39 if (f.default_value) |d| {
40 break :brk @as(*const f.type, @alignCast(@ptrCast(d))).*;
41 }
42 return err;
43 };
44 // DER encodes null values by skipping them.
45 if (@typeInfo(f.type) == .Optional and @field(res, f.name) == null) {
46 if (f.default_value) |d| {
47 @field(res, f.name) = @as(*const f.type, @alignCast(@ptrCast(d))).*;
48 }
49 }
50 }
51
52 return res;
53 },
54 .Bool => {
55 const ele = try self.element(tag);
56 const bytes = self.view(ele);
57 if (bytes.len != 1) return error.InvalidBool;
58
59 return switch (bytes[0]) {
60 0x00 => false,
61 0xff => true,
62 else => error.InvalidBool,
63 };
64 },
65 .Int => {
66 const ele = try self.element(tag);
67 const bytes = self.view(ele);
68 return try int(T, bytes);
69 },
70 .Enum => |e| {
71 const ele = try self.element(tag);
72 const bytes = self.view(ele);
73 if (@hasDecl(T, "oids")) {
74 return T.oids.oidToEnum(bytes) orelse return error.UnknownOid;
75 }
76 return @enumFromInt(try int(e.tag_type, bytes));
77 },
78 .Optional => |o| return self.any(o.child) catch return null,
79 else => @compileError("cannot decode type " ++ @typeName(T)),
80 }
81}
82
83pub fn sequence(self: *Decoder) !Element {
84 return try self.element(ExpectedTag.init(.sequence, true, .universal));
85}
86
87pub fn element(self: *Decoder, expected: ExpectedTag) (error{ EndOfStream, UnexpectedElement } || Element.DecodeError)!Element {
88 if (self.index >= self.bytes.len) return error.EndOfStream;
89
90 const res = try Element.decode(self.bytes, self.index);
91 var e = expected;
92 if (self.field_tag) |ft| {
93 e.number = @enumFromInt(ft.number);
94 e.class = ft.class;
95 }
96 if (!e.match(res.tag)) {
97 return error.UnexpectedElement;
98 }
99
100 self.index = if (res.tag.constructed) res.slice.start else res.slice.end;
101 return res;
102}
103
104pub fn view(self: Decoder, elem: Element) []const u8 {
105 return elem.slice.view(self.bytes);
106}
107
108fn int(comptime T: type, value: []const u8) error{ NonCanonical, LargeValue }!T {
109 if (@typeInfo(T).Int.bits % 8 != 0) @compileError("T must be byte aligned");
110
111 var bytes = value;
112 if (bytes.len >= 2) {
113 if (bytes[0] == 0) {
114 if (@clz(bytes[1]) > 0) return error.NonCanonical;
115 bytes.ptr += 1;
116 }
117 if (bytes[0] == 0xff and @clz(bytes[1]) == 0) return error.NonCanonical;
118 }
119
120 if (bytes.len > @sizeOf(T)) return error.LargeValue;
121 if (@sizeOf(T) == 1) return @bitCast(bytes[0]);
122
123 return std.mem.readVarInt(T, bytes, .big);
124}
125
126test int {
127 try expectEqual(@as(u8, 1), try int(u8, &[_]u8{1}));
128 try expectError(error.NonCanonical, int(u8, &[_]u8{ 0, 1 }));
129 try expectError(error.NonCanonical, int(u8, &[_]u8{ 0xff, 0xff }));
130
131 const big = [_]u8{ 0xef, 0xff };
132 try expectError(error.LargeValue, int(u8, &big));
133 try expectEqual(0xefff, int(u16, &big));
134}
135
136test Decoder {
137 var parser = Decoder{ .bytes = @embedFile("./testdata/id_ecc.pub.der") };
138 const seq = try parser.sequence();
139
140 {
141 const seq2 = try parser.sequence();
142 _ = try parser.element(ExpectedTag.init(.oid, false, .universal));
143 _ = try parser.element(ExpectedTag.init(.oid, false, .universal));
144
145 try std.testing.expectEqual(parser.index, seq2.slice.end);
146 }
147 _ = try parser.element(ExpectedTag.init(.bitstring, false, .universal));
148
149 try std.testing.expectEqual(parser.index, seq.slice.end);
150 try std.testing.expectEqual(parser.index, parser.bytes.len);
151}
152
153const std = @import("std");
154const builtin = @import("builtin");
155const asn1 = @import("../../asn1.zig");
156const Oid = @import("../Oid.zig");
157
158const expectEqual = std.testing.expectEqual;
159const expectError = std.testing.expectError;
160const Decoder = @This();
161const Index = asn1.Index;
162const Tag = asn1.Tag;
163const FieldTag = asn1.FieldTag;
164const ExpectedTag = asn1.ExpectedTag;
165const Element = asn1.Element;
lib/std/crypto/asn1/der/Encoder.zig created+162
......@@ -0,0 +1,162 @@
1//! A buffered DER encoder.
2//!
3//! Prefers calling container's `fn encodeDer(self: @This(), encoder: *der.Encoder)`.
4//! That function should encode values, lengths, then tags.
5buffer: ArrayListReverse,
6/// The field tag set by a parent container.
7/// This is needed because we might visit an implicitly tagged container with a `fn encodeDer`.
8field_tag: ?FieldTag = null,
9
10pub fn init(allocator: std.mem.Allocator) Encoder {
11 return Encoder{ .buffer = ArrayListReverse.init(allocator) };
12}
13
14pub fn deinit(self: *Encoder) void {
15 self.buffer.deinit();
16}
17
18pub fn any(self: *Encoder, val: anytype) !void {
19 const T = @TypeOf(val);
20 try self.anyTag(Tag.fromZig(T), val);
21}
22
23fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
24 const T = @TypeOf(val);
25 if (std.meta.hasFn(T, "encodeDer")) return try val.encodeDer(self);
26 const start = self.buffer.data.len;
27 const merged_tag = self.mergedTag(tag_);
28
29 switch (@typeInfo(T)) {
30 .Struct => |info| {
31 inline for (0..info.fields.len) |i| {
32 const f = info.fields[info.fields.len - i - 1];
33 const field_val = @field(val, f.name);
34 const field_tag = FieldTag.fromContainer(T, f.name);
35
36 // > The encoding of a set value or sequence value shall not include an encoding for any
37 // > component value which is equal to its default value.
38 const is_default = if (f.is_comptime) false else if (f.default_value) |v| brk: {
39 const default_val: *const f.type = @alignCast(@ptrCast(v));
40 break :brk std.mem.eql(u8, std.mem.asBytes(default_val), std.mem.asBytes(&field_val));
41 } else false;
42
43 if (!is_default) {
44 const start2 = self.buffer.data.len;
45 self.field_tag = field_tag;
46 // will merge with self.field_tag.
47 // may mutate self.field_tag.
48 try self.anyTag(Tag.fromZig(f.type), field_val);
49 if (field_tag) |ft| {
50 if (ft.explicit) {
51 try self.length(self.buffer.data.len - start2);
52 try self.tag(ft.toTag());
53 self.field_tag = null;
54 }
55 }
56 }
57 }
58 },
59 .Bool => try self.buffer.prependSlice(&[_]u8{if (val) 0xff else 0}),
60 .Int => try self.int(T, val),
61 .Enum => |e| {
62 if (@hasDecl(T, "oids")) {
63 return self.any(T.oids.enumToOid(val));
64 } else {
65 try self.int(e.tag_type, @intFromEnum(val));
66 }
67 },
68 .Optional => if (val) |v| return try self.anyTag(tag_, v),
69 .Null => {},
70 else => @compileError("cannot encode type " ++ @typeName(T)),
71 }
72
73 try self.length(self.buffer.data.len - start);
74 try self.tag(merged_tag);
75}
76
77pub fn tag(self: *Encoder, tag_: Tag) !void {
78 const t = self.mergedTag(tag_);
79 try t.encode(self.writer());
80}
81
82pub fn tagBytes(self: *Encoder, tag_: Tag, bytes: []const u8) !void {
83 try self.buffer.prependSlice(bytes);
84 try self.length(bytes.len);
85 try self.tag(tag_);
86}
87
88fn mergedTag(self: *Encoder, tag_: Tag) Tag {
89 var res = tag_;
90 if (self.field_tag) |ft| {
91 if (!ft.explicit) {
92 res.number = @enumFromInt(ft.number);
93 res.class = ft.class;
94 }
95 }
96 return res;
97}
98
99pub fn length(self: *Encoder, len: usize) !void {
100 const writer_ = self.writer();
101 if (len < 128) {
102 try writer_.writeInt(u8, @intCast(len), .big);
103 return;
104 }
105 inline for ([_]type{ u8, u16, u32 }) |T| {
106 if (len < std.math.maxInt(T)) {
107 try writer_.writeInt(T, @intCast(len), .big);
108 try writer_.writeInt(u8, @sizeOf(T) | 0x80, .big);
109 return;
110 }
111 }
112 return error.InvalidLength;
113}
114
115/// Warning: This writer writes backwards. `fn print` will NOT work as expected.
116pub fn writer(self: *Encoder) ArrayListReverse.Writer {
117 return self.buffer.writer();
118}
119
120fn int(self: *Encoder, comptime T: type, value: T) !void {
121 const big = std.mem.nativeTo(T, value, .big);
122 const big_bytes = std.mem.asBytes(&big);
123
124 const bits_needed = @bitSizeOf(T) - @clz(value);
125 const needs_padding: u1 = if (value == 0)
126 1
127 else if (bits_needed > 8) brk: {
128 const RightShift = std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(bits_needed)) - 1);
129 const right_shift: RightShift = @intCast(bits_needed - 9);
130 break :brk if (value >> right_shift == 0x1ff) 1 else 0;
131 } else 0;
132 const bytes_needed = try std.math.divCeil(usize, bits_needed, 8) + needs_padding;
133
134 const writer_ = self.writer();
135 for (0..bytes_needed - needs_padding) |i| try writer_.writeByte(big_bytes[big_bytes.len - i - 1]);
136 if (needs_padding == 1) try writer_.writeByte(0);
137}
138
139test int {
140 const allocator = std.testing.allocator;
141 var encoder = Encoder.init(allocator);
142 defer encoder.deinit();
143
144 try encoder.int(u8, 0);
145 try std.testing.expectEqualSlices(u8, &[_]u8{0}, encoder.buffer.data);
146
147 encoder.buffer.clearAndFree();
148 try encoder.int(u16, 0x00ff);
149 try std.testing.expectEqualSlices(u8, &[_]u8{0xff}, encoder.buffer.data);
150
151 encoder.buffer.clearAndFree();
152 try encoder.int(u32, 0xffff);
153 try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0xff, 0xff }, encoder.buffer.data);
154}
155
156const std = @import("std");
157const Oid = @import("../Oid.zig");
158const asn1 = @import("../../asn1.zig");
159const ArrayListReverse = @import("./ArrayListReverse.zig");
160const Tag = asn1.Tag;
161const FieldTag = asn1.FieldTag;
162const Encoder = @This();
lib/std/crypto/asn1/der/testdata/all_types.der created
Binary files /dev/null and b/lib/std/crypto/asn1/der/testdata/all_types.der differ
lib/std/crypto/asn1/der/testdata/id_ecc.pub.der created
Binary files /dev/null and b/lib/std/crypto/asn1/der/testdata/id_ecc.pub.der differ
lib/std/crypto/asn1/test.zig created+79
......@@ -0,0 +1,79 @@
1const std = @import("std");
2const asn1 = @import("../asn1.zig");
3
4const der = asn1.der;
5const Tag = asn1.Tag;
6const FieldTag = asn1.FieldTag;
7
8/// An example that uses all ASN1 types and available implementation features.
9const AllTypes = struct {
10 a: u8 = 0,
11 b: asn1.BitString,
12 c: C,
13 d: asn1.Opaque(Tag.universal(.string_utf8, false)),
14 e: asn1.Opaque(Tag.universal(.octetstring, false)),
15 f: ?u16,
16 g: ?Nested,
17 h: asn1.Any,
18
19 pub const asn1_tags = .{
20 .a = FieldTag.explicit(0, .context_specific),
21 .b = FieldTag.explicit(1, .context_specific),
22 .c = FieldTag.implicit(2, .context_specific),
23 .g = FieldTag.implicit(3, .context_specific),
24 };
25
26 const C = enum {
27 a,
28 b,
29
30 pub const oids = asn1.Oid.StaticMap(@This()).initComptime(.{
31 .a = "1.2.3.4",
32 .b = "1.2.3.5",
33 });
34 };
35
36 const Nested = struct {
37 inner: Asn1T,
38 sum: i16,
39
40 const Asn1T = struct { a: u8, b: i16 };
41
42 pub fn decodeDer(decoder: *der.Decoder) !Nested {
43 const inner = try decoder.any(Asn1T);
44 return Nested{ .inner = inner, .sum = inner.a + inner.b };
45 }
46
47 pub fn encodeDer(self: Nested, encoder: *der.Encoder) !void {
48 try encoder.any(self.inner);
49 }
50 };
51};
52
53test AllTypes {
54 const expected = AllTypes{
55 .a = 2,
56 .b = asn1.BitString{ .bytes = &[_]u8{ 0x04, 0xa0 } },
57 .c = .a,
58 .d = .{ .bytes = "asdf" },
59 .e = .{ .bytes = "fdsa" },
60 .f = (1 << 8) + 1,
61 .g = .{ .inner = .{ .a = 4, .b = 5 }, .sum = 9 },
62 .h = .{ .tag = Tag.init(.string_ia5, false, .universal), .bytes = "asdf" },
63 };
64 const path = "./der/testdata/all_types.der";
65 const encoded = @embedFile(path);
66 const actual = try asn1.der.decode(AllTypes, encoded);
67 try std.testing.expectEqualDeep(expected, actual);
68
69 const allocator = std.testing.allocator;
70 const buf = try asn1.der.encode(allocator, expected);
71 defer allocator.free(buf);
72 try std.testing.expectEqualSlices(u8, encoded, buf);
73
74 // Use this to update test file.
75 // const dir = try std.fs.cwd().openDir("lib/std/crypto/asn1", .{});
76 // var file = try dir.createFile(path, .{});
77 // defer file.close();
78 // try file.writeAll(buf);
79}