authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2021-01-31 12:01:34 -08:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2021-01-31 12:01:34 -08:00
log9233f771c736879cd42d07b1aa1cacf29e99ef64
tree44c76fc620846636adf963830e204646386fb399
parent194ac2fd5bc3580e12ededb3b51df3e3cfa7a9d6

add zuri and iguanatls submodules


24 files changed, 5099 insertions(+), 0 deletions(-)

.gitmodules+6
...@@ -7,3 +7,9 @@...@@ -7,3 +7,9 @@
7[submodule "libs/zig-ansi"]7[submodule "libs/zig-ansi"]
8 path = libs/zig-ansi8 path = libs/zig-ansi
9 url = https://github.com/nektro/zig-ansi9 url = https://github.com/nektro/zig-ansi
10[submodule "libs/zuri"]
11 path = libs/zuri
12 url = https://github.com/Vexu/zuri
13[submodule "libs/iguanatls"]
14 path = libs/iguanatls
15 url = https://github.com/alexnask/iguanaTLS
build.zig+2
...@@ -38,6 +38,8 @@ pub fn build(b: *Builder) void {...@@ -38,6 +38,8 @@ pub fn build(b: *Builder) void {
3838
39 exe.addPackagePath("known-folders", "./libs/zig-known-folders/known-folders.zig");39 exe.addPackagePath("known-folders", "./libs/zig-known-folders/known-folders.zig");
40 exe.addPackagePath("ansi", "./libs/zig-ansi/src/lib.zig");40 exe.addPackagePath("ansi", "./libs/zig-ansi/src/lib.zig");
41 exe.addPackagePath("zuri", "./libs/zuri/src/zuri.zig");
42 exe.addPackagePath("iguanatls", "./libs/iguanatls/src/main.zig");
4143
42 exe.install();44 exe.install();
4345
libs/iguanatls/.gitattributes created+1
...@@ -0,0 +1 @@
1*.zig text=auto eol=lf
libs/iguanatls/.gitignore created+3
...@@ -0,0 +1,3 @@
1/zig-cache
2deps.zig
3gyro.lock
libs/iguanatls/.gitmodules
libs/iguanatls/LICENSE created+21
...@@ -0,0 +1,21 @@
1MIT License
2
3Copyright (c) 2020 Alexandros Naskos
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in all
13copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21SOFTWARE.
libs/iguanatls/build.zig created+14
...@@ -0,0 +1,14 @@
1const Builder = @import("std").build.Builder;
2
3pub fn build(b: *Builder) void {
4 const mode = b.standardReleaseOptions();
5 const lib = b.addStaticLibrary("iguanaTLS", "src/main.zig");
6 lib.setBuildMode(mode);
7 lib.install();
8
9 var main_tests = b.addTest("src/main.zig");
10 main_tests.setBuildMode(mode);
11
12 const test_step = b.step("test", "Run library tests");
13 test_step.dependOn(&main_tests.step);
14}
libs/iguanatls/gyro.zzz created+14
...@@ -0,0 +1,14 @@
1pkgs:
2 iguanaTLS:
3 version: 0.0.0
4 author: alexnask
5 description: "Minimal, experimental TLS 1.2 implementation in Zig"
6 license: MIT
7 source_url: "https://github.com/alexnask/iguanaTLS"
8
9 files:
10 build.zig
11 README.md
12 LICENSE
13 src/*.zig
14 test/*
libs/iguanatls/src/asn1.zig created+622
...@@ -0,0 +1,622 @@
1const std = @import("std");
2const BigInt = std.math.big.int.Const;
3const mem = std.mem;
4const Allocator = mem.Allocator;
5const ArenaAllocator = std.heap.ArenaAllocator;
6
7// zig fmt: off
8pub const Tag = enum(u8) {
9 bool = 0x01,
10 int = 0x02,
11 bit_string = 0x03,
12 octet_string = 0x04,
13 @"null" = 0x05,
14 object_identifier = 0x06,
15 utf8_string = 0x0c,
16 printable_string = 0x13,
17 ia5_string = 0x16,
18 utc_time = 0x17,
19 bmp_string = 0x1e,
20 sequence = 0x30,
21 set = 0x31,
22 // Bogus value
23 context_specific = 0xff,
24};
25// zig fmt: on
26
27pub const ObjectIdentifier = struct {
28 data: [16]u32,
29 len: u8,
30};
31
32pub const BitString = struct {
33 data: []const u8,
34 bit_len: usize,
35};
36
37pub const Value = union(Tag) {
38 bool: bool,
39 int: BigInt,
40 bit_string: BitString,
41 octet_string: []const u8,
42 @"null",
43 // @TODO Make this []u32, owned?
44 object_identifier: ObjectIdentifier,
45 utf8_string: []const u8,
46 printable_string: []const u8,
47 ia5_string: []const u8,
48 utc_time: []const u8,
49 bmp_string: []const u16,
50 sequence: []const @This(),
51 set: []const @This(),
52 context_specific: struct {
53 child: *const Value,
54 number: u8,
55 },
56
57 pub fn deinit(self: @This(), alloc: *Allocator) void {
58 switch (self) {
59 .int => |i| alloc.free(i.limbs),
60 .bit_string => |bs| alloc.free(bs.data),
61 .octet_string,
62 .utf8_string,
63 .printable_string,
64 .ia5_string,
65 .utc_time,
66 => |s| alloc.free(s),
67 .bmp_string => |s| alloc.free(s),
68 .sequence, .set => |s| {
69 for (s) |c| {
70 c.deinit(alloc);
71 }
72 alloc.free(s);
73 },
74 .context_specific => |cs| {
75 cs.child.deinit(alloc);
76 alloc.destroy(cs.child);
77 },
78 else => {},
79 }
80 }
81
82 fn formatInternal(
83 self: Value,
84 comptime fmt: []const u8,
85 options: std.fmt.FormatOptions,
86 indents: usize,
87 writer: anytype,
88 ) @TypeOf(writer).Error!void {
89 try writer.writeByteNTimes(' ', indents);
90 switch (self) {
91 .bool => |b| try writer.print("BOOLEAN {}\n", .{b}),
92 .int => |i| {
93 try writer.writeAll("INTEGER ");
94 try i.format(fmt, options, writer);
95 try writer.writeByte('\n');
96 },
97 .bit_string => |bs| {
98 try writer.print("BIT STRING ({} bits) ", .{bs.bit_len});
99 const bits_to_show = std.math.min(8 * 3, bs.bit_len);
100 const bytes = std.math.divCeil(usize, bits_to_show, 8) catch unreachable;
101
102 var bit_idx: usize = 0;
103 var byte_idx: usize = 0;
104 while (byte_idx < bytes) : (byte_idx += 1) {
105 const byte = bs.data[byte_idx];
106 var cur_bit_idx: u3 = 0;
107 while (bit_idx < bits_to_show) {
108 const mask = @as(u8, 0x80) >> cur_bit_idx;
109 try writer.print("{}", .{@boolToInt(byte & mask == mask)});
110 cur_bit_idx += 1;
111 bit_idx += 1;
112 if (cur_bit_idx == 7)
113 break;
114 }
115 }
116 if (bits_to_show != bs.bit_len)
117 try writer.writeAll("...");
118 try writer.writeByte('\n');
119 },
120 .octet_string => |s| try writer.print("OCTET STRING ({} bytes) {X}\n", .{ s.len, s }),
121 .@"null" => try writer.writeAll("NULL\n"),
122 .object_identifier => |oid| {
123 try writer.writeAll("OBJECT IDENTIFIER ");
124 var i: u8 = 0;
125 while (i < oid.len) : (i += 1) {
126 if (i != 0) try writer.writeByte('.');
127 try writer.print("{}", .{oid.data[i]});
128 }
129 try writer.writeByte('\n');
130 },
131 .utf8_string => |s| try writer.print("UTF8 STRING ({} bytes) {}\n", .{ s.len, s }),
132 .printable_string => |s| try writer.print("PRINTABLE STRING ({} bytes) {}\n", .{ s.len, s }),
133 .ia5_string => |s| try writer.print("IA5 STRING ({} bytes) {}\n", .{ s.len, s }),
134 .utc_time => |s| try writer.print("UTC TIME {}\n", .{s}),
135 .bmp_string => |s| try writer.print("BMP STRING ({} words) {}\n", .{
136 s.len,
137 @ptrCast([*]const u16, s.ptr)[0 .. s.len * 2],
138 }),
139 .sequence => |children| {
140 try writer.print("SEQUENCE ({} elems)\n", .{children.len});
141 for (children) |child| try child.formatInternal(fmt, options, indents + 2, writer);
142 },
143 .set => |children| {
144 try writer.print("SET ({} elems)\n", .{children.len});
145 for (children) |child| try child.formatInternal(fmt, options, indents + 2, writer);
146 },
147 .context_specific => |cs| {
148 try writer.print("[{}]\n", .{cs.number});
149 try cs.child.formatInternal(fmt, options, indents + 2, writer);
150 },
151 }
152 }
153
154 pub fn format(self: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
155 try self.formatInternal(fmt, options, 0, writer);
156 }
157};
158
159/// Distinguished encoding rules
160pub const der = struct {
161 pub fn DecodeError(comptime Reader: type) type {
162 return Reader.Error || error{
163 OutOfMemory,
164 EndOfStream,
165 InvalidLength,
166 InvalidTag,
167 InvalidContainerLength,
168 DoesNotMatchSchema,
169 };
170 }
171
172 fn DERReaderState(comptime Reader: type) type {
173 return struct {
174 der_reader: Reader,
175 length: usize,
176 idx: usize = 0,
177 };
178 }
179
180 fn DERReader(comptime Reader: type) type {
181 const S = struct {
182 pub fn read(state: *DERReaderState(Reader), buffer: []u8) DecodeError(Reader)!usize {
183 const out_bytes = std.math.min(buffer.len, state.length - state.idx);
184 const res = try state.der_reader.readAll(buffer[0..out_bytes]);
185 state.idx += res;
186 return res;
187 }
188 };
189
190 return std.io.Reader(*DERReaderState(Reader), DecodeError(Reader), S.read);
191 }
192
193 pub fn parse_schema(
194 schema: anytype,
195 captures: anytype,
196 der_reader: anytype,
197 ) !void {
198 const res = try parse_schema_tag_len_internal(null, null, schema, captures, der_reader);
199 if (res != null) return error.DoesNotMatchSchema;
200 }
201
202 pub fn parse_schema_tag_len(
203 existing_tag_byte: ?u8,
204 existing_length: ?usize,
205 schema: anytype,
206 captures: anytype,
207 der_reader: anytype,
208 ) !void {
209 const res = try parse_schema_tag_len_internal(
210 existing_tag_byte,
211 existing_length,
212 schema,
213 captures,
214 der_reader,
215 );
216 if (res != null) return error.DoesNotMatchSchema;
217 }
218
219 const TagLength = struct {
220 tag: u8,
221 length: usize,
222 };
223
224 pub fn parse_schema_tag_len_internal(
225 existing_tag_byte: ?u8,
226 existing_length: ?usize,
227 schema: anytype,
228 captures: anytype,
229 der_reader: anytype,
230 ) !?TagLength {
231 const Reader = @TypeOf(der_reader);
232
233 const isEnumLit = comptime std.meta.trait.is(.EnumLiteral);
234 comptime var tag_idx = 0;
235
236 const has_capture = comptime isEnumLit(@TypeOf(schema[tag_idx])) and schema[tag_idx] == .capture;
237 if (has_capture) tag_idx += 2;
238
239 const is_optional = comptime isEnumLit(@TypeOf(schema[tag_idx])) and schema[tag_idx] == .optional;
240 if (is_optional) tag_idx += 1;
241
242 const tag_literal = schema[tag_idx];
243 comptime std.debug.assert(isEnumLit(@TypeOf(tag_literal)));
244
245 const tag_byte = existing_tag_byte orelse (der_reader.readByte() catch |err| switch (err) {
246 error.EndOfStream => |e| return if (is_optional) null else error.EndOfStream,
247 else => |e| return e,
248 });
249
250 const length = existing_length orelse try parse_length(der_reader);
251 if (tag_literal == .sequence_of) {
252 if (tag_byte != @enumToInt(Tag.sequence)) {
253 if (is_optional) return TagLength{ .tag = tag_byte, .length = length };
254 return error.InvalidTag;
255 }
256
257 var curr_tag_length: ?TagLength = null;
258 const sub_schema = schema[tag_idx + 1];
259 while (true) {
260 if (curr_tag_length == null) {
261 curr_tag_length = .{
262 .tag = der_reader.readByte() catch |err| switch (err) {
263 error.EndOfStream => {
264 curr_tag_length = null;
265 break;
266 },
267 else => |e| return e,
268 },
269 .length = try parse_length(der_reader),
270 };
271 }
272
273 curr_tag_length = parse_schema_tag_len_internal(
274 curr_tag_length.?.tag,
275 curr_tag_length.?.length,
276 sub_schema,
277 captures,
278 der_reader,
279 ) catch |err| switch (err) {
280 error.DoesNotMatchSchema => break,
281 else => |e| return e,
282 };
283 }
284 return curr_tag_length;
285 } else if (tag_literal == .any) {
286 if (!has_capture) {
287 try der_reader.skipBytes(length, .{});
288 return null;
289 }
290
291 var reader_state = DERReaderState(Reader){
292 .der_reader = der_reader,
293 .idx = 0,
294 .length = length,
295 };
296 var reader = DERReader(@TypeOf(der_reader)){ .context = &reader_state };
297 const capture_context = captures[schema[1] * 2];
298 const capture_action = captures[schema[1] * 2 + 1];
299 try capture_action(capture_context, tag_byte, length, reader);
300
301 // Skip remaining bytes
302 try der_reader.skipBytes(reader_state.length - reader_state.idx, .{});
303 return null;
304 } else if (tag_literal == .context_specific) {
305 const cs_number = schema[tag_idx + 1];
306 if (tag_byte & 0xC0 == 0x80 and tag_byte - 0xa0 == cs_number) {
307 if (!has_capture) {
308 if (schema.len > tag_idx + 2) {
309 return try parse_schema_tag_len_internal(null, null, schema[tag_idx + 2], captures, der_reader);
310 }
311
312 try der_reader.skipBytes(length, .{});
313 return null;
314 }
315
316 var reader_state = DERReaderState(Reader){
317 .der_reader = der_reader,
318 .idx = 0,
319 .length = length,
320 };
321 var reader = DERReader(Reader){ .context = &reader_state };
322 const capture_context = captures[schema[1] * 2];
323 const capture_action = captures[schema[1] * 2 + 1];
324 try capture_action(capture_context, tag_byte, length, reader);
325
326 // Skip remaining bytes
327 try der_reader.skipBytes(reader_state.length - reader_state.idx, .{});
328 return null;
329 } else if (is_optional)
330 return TagLength{ .tag = tag_byte, .length = length }
331 else
332 return error.DoesNotMatchSchema;
333 }
334
335 const schema_tag: Tag = tag_literal;
336 const actual_tag = std.meta.intToEnum(Tag, tag_byte) catch return error.InvalidTag;
337 if (actual_tag != schema_tag) {
338 if (is_optional) return tag_byte;
339 return error.DoesNotMatchSchema;
340 }
341
342 const single_seq = schema_tag == .sequence and schema.len == 1;
343 if ((!has_capture and schema_tag != .sequence) or (!has_capture and single_seq)) {
344 try der_reader.skipBytes(length, .{});
345 return null;
346 }
347
348 if (has_capture) {
349 var reader_state = DERReaderState(Reader){
350 .der_reader = der_reader,
351 .idx = 0,
352 .length = length,
353 };
354 var reader = DERReader(Reader){ .context = &reader_state };
355 const capture_context = captures[schema[1] * 2];
356 const capture_action = captures[schema[1] * 2 + 1];
357 try capture_action(capture_context, tag_byte, length, reader);
358
359 // Skip remaining bytes
360 try der_reader.skipBytes(reader_state.length - reader_state.idx, .{});
361 return null;
362 }
363
364 var cur_tag_length: ?TagLength = null;
365 const sub_schemas = schema[tag_idx + 1];
366 comptime var i = 0;
367 inline while (i < sub_schemas.len) : (i += 1) {
368 const curr_tag = if (cur_tag_length) |tl| tl.tag else null;
369 const curr_length = if (cur_tag_length) |tl| tl.length else null;
370 cur_tag_length = try parse_schema_tag_len_internal(curr_tag, curr_length, sub_schemas[i], captures, der_reader);
371 }
372 return cur_tag_length;
373 }
374
375 pub const EncodedLength = struct {
376 data: [@sizeOf(usize) + 1]u8,
377 len: usize,
378
379 pub fn slice(self: @This()) []const u8 {
380 if (self.len == 1) return self.data[0..1];
381 return self.data[0 .. 1 + self.len];
382 }
383 };
384
385 pub fn encode_length(length: usize) EncodedLength {
386 var enc = EncodedLength{ .data = undefined, .len = 0 };
387 if (length < 128) {
388 enc.data[0] = @truncate(u8, length);
389 enc.len = 1;
390 } else {
391 const bytes_needed = @intCast(u8, std.math.divCeil(
392 usize,
393 std.math.log2_int_ceil(usize, length),
394 8,
395 ) catch unreachable);
396 enc.data[0] = bytes_needed | 0x80;
397 mem.copy(
398 u8,
399 enc.data[1 .. bytes_needed + 1],
400 mem.asBytes(&length)[0..bytes_needed],
401 );
402 if (std.builtin.endian != .Big) {
403 mem.reverse(u8, enc.data[1 .. bytes_needed + 1]);
404 }
405 enc.len = bytes_needed;
406 }
407 return enc;
408 }
409
410 fn parse_int_internal(alloc: *Allocator, bytes_read: *usize, der_reader: anytype) !BigInt {
411 const length = try parse_length_internal(bytes_read, der_reader);
412 const first_byte = try der_reader.readByte();
413 if (first_byte == 0x0 and length > 1) {
414 // Positive number with highest bit set to 1 in the rest.
415 const limb_count = std.math.divCeil(usize, length - 1, @sizeOf(usize)) catch unreachable;
416 const limbs = try alloc.alloc(usize, limb_count);
417 std.mem.set(usize, limbs, 0);
418 errdefer alloc.free(limbs);
419
420 var limb_ptr = @ptrCast([*]u8, limbs.ptr);
421 try der_reader.readNoEof(limb_ptr[0 .. length - 1]);
422 // We always reverse because the standard library big int expects little endian.
423 mem.reverse(u8, limb_ptr[0 .. length - 1]);
424
425 bytes_read.* += length;
426 return BigInt{ .limbs = limbs, .positive = true };
427 }
428 std.debug.assert(length != 0);
429 // Write first_byte
430 // Twos complement
431 const limb_count = std.math.divCeil(usize, length, @sizeOf(usize)) catch unreachable;
432 const limbs = try alloc.alloc(usize, limb_count);
433 std.mem.set(usize, limbs, 0);
434 errdefer alloc.free(limbs);
435
436 var limb_ptr = @ptrCast([*]u8, limbs.ptr);
437 limb_ptr[0] = first_byte & ~@as(u8, 0x80);
438 try der_reader.readNoEof(limb_ptr[1..length]);
439
440 // We always reverse because the standard library big int expects little endian.
441 mem.reverse(u8, limb_ptr[0..length]);
442 bytes_read.* += length;
443 return BigInt{ .limbs = limbs, .positive = (first_byte & 0x80) == 0x00 };
444 }
445
446 pub fn parse_int(alloc: *Allocator, der_reader: anytype) !BigInt {
447 var bytes: usize = undefined;
448 return try parse_int_internal(alloc, &bytes, der_reader);
449 }
450
451 pub fn parse_length(der_reader: anytype) !usize {
452 var bytes: usize = 0;
453 return try parse_length_internal(&bytes, der_reader);
454 }
455
456 fn parse_length_internal(bytes_read: *usize, der_reader: anytype) !usize {
457 const first_byte = try der_reader.readByte();
458 bytes_read.* += 1;
459 if (first_byte & 0x80 == 0x00) {
460 // 1 byte value
461 return first_byte;
462 }
463 const length = @truncate(u7, first_byte);
464 if (length > @sizeOf(usize))
465 @panic("DER length does not fit in usize");
466
467 var res_buf = std.mem.zeroes([@sizeOf(usize)]u8);
468 try der_reader.readNoEof(res_buf[0..length]);
469 bytes_read.* += length;
470
471 if (std.builtin.endian != .Big) {
472 mem.reverse(u8, res_buf[0..length]);
473 }
474 return mem.bytesToValue(usize, &res_buf);
475 }
476
477 fn parse_value_with_tag_byte(
478 tag_byte: u8,
479 alloc: *Allocator,
480 bytes_read: *usize,
481 der_reader: anytype,
482 ) DecodeError(@TypeOf(der_reader))!Value {
483 const tag = std.meta.intToEnum(Tag, tag_byte) catch {
484 // tag starts with '0b10...', this is the context specific class.
485 if (tag_byte & 0xC0 == 0x80) {
486 const length = try parse_length_internal(bytes_read, der_reader);
487 var cur_read_bytes: usize = 0;
488 var child = try alloc.create(Value);
489 errdefer alloc.destroy(child);
490
491 child.* = try parse_value_internal(alloc, &cur_read_bytes, der_reader);
492 if (cur_read_bytes != length)
493 return error.InvalidContainerLength;
494 bytes_read.* += length;
495 return Value{ .context_specific = .{ .child = child, .number = tag_byte - 0xa0 } };
496 }
497
498 return error.InvalidTag;
499 };
500 switch (tag) {
501 .bool => {
502 if ((try der_reader.readByte()) != 0x1)
503 return error.InvalidLength;
504 defer bytes_read.* += 2;
505 return Value{ .bool = (try der_reader.readByte()) != 0x0 };
506 },
507 .int => return Value{ .int = try parse_int_internal(alloc, bytes_read, der_reader) },
508 .bit_string => {
509 const length = try parse_length_internal(bytes_read, der_reader);
510 const unused_bits = try der_reader.readByte();
511 std.debug.assert(unused_bits < 8);
512 const bit_count = (length - 1) * 8 - unused_bits;
513 const bit_memory = try alloc.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable);
514 errdefer alloc.free(bit_memory);
515 try der_reader.readNoEof(bit_memory[0 .. length - 1]);
516
517 bytes_read.* += length;
518 return Value{ .bit_string = .{ .data = bit_memory, .bit_len = bit_count } };
519 },
520 .octet_string, .utf8_string, .printable_string, .utc_time, .ia5_string => {
521 const length = try parse_length_internal(bytes_read, der_reader);
522 const str_mem = try alloc.alloc(u8, length);
523 try der_reader.readNoEof(str_mem);
524 bytes_read.* += length;
525 return @as(Value, switch (tag) {
526 .octet_string => .{ .octet_string = str_mem },
527 .utf8_string => .{ .utf8_string = str_mem },
528 .printable_string => .{ .printable_string = str_mem },
529 .utc_time => .{ .utc_time = str_mem },
530 .ia5_string => .{ .ia5_string = str_mem },
531 else => unreachable,
532 });
533 },
534 .@"null" => {
535 std.debug.assert((try parse_length_internal(bytes_read, der_reader)) == 0x00);
536 return .@"null";
537 },
538 .object_identifier => {
539 const length = try parse_length_internal(bytes_read, der_reader);
540 const first_byte = try der_reader.readByte();
541 var ret = Value{ .object_identifier = .{ .data = undefined, .len = 0 } };
542 ret.object_identifier.data[0] = first_byte / 40;
543 ret.object_identifier.data[1] = first_byte % 40;
544
545 var out_idx: u8 = 2;
546 var i: usize = 0;
547 while (i < length - 1) {
548 var current_value: u32 = 0;
549 var current_byte = try der_reader.readByte();
550 i += 1;
551 while (current_byte & 0x80 == 0x80) : (i += 1) {
552 // Increase the base of the previous bytes
553 current_value *= 128;
554 // Add the current byte in base 128
555 current_value += @as(u32, current_byte & ~@as(u8, 0x80)) * 128;
556 current_byte = try der_reader.readByte();
557 } else {
558 current_value += current_byte;
559 }
560 ret.object_identifier.data[out_idx] = current_value;
561 out_idx += 1;
562 }
563 ret.object_identifier.len = out_idx;
564 std.debug.assert(out_idx <= 16);
565 bytes_read.* += length;
566 return ret;
567 },
568 .bmp_string => {
569 const length = try parse_length_internal(bytes_read, der_reader);
570 const str_mem = try alloc.alloc(u16, @divExact(length, 2));
571 errdefer alloc.free(str_mem);
572
573 for (str_mem) |*wide_char| {
574 wide_char.* = try der_reader.readIntBig(u16);
575 }
576 bytes_read.* += length;
577 return Value{ .bmp_string = str_mem };
578 },
579 .sequence, .set => {
580 const length = try parse_length_internal(bytes_read, der_reader);
581 var cur_read_bytes: usize = 0;
582 var arr = std.ArrayList(Value).init(alloc);
583 errdefer arr.deinit();
584
585 while (cur_read_bytes < length) {
586 (try arr.addOne()).* = try parse_value_internal(alloc, &cur_read_bytes, der_reader);
587 }
588 if (cur_read_bytes != length)
589 return error.InvalidContainerLength;
590 bytes_read.* += length;
591
592 return @as(Value, switch (tag) {
593 .sequence => .{ .sequence = arr.toOwnedSlice() },
594 .set => .{ .set = arr.toOwnedSlice() },
595 else => unreachable,
596 });
597 },
598 .context_specific => unreachable,
599 }
600 }
601
602 fn parse_value_internal(alloc: *Allocator, bytes_read: *usize, der_reader: anytype) DecodeError(@TypeOf(der_reader))!Value {
603 const tag_byte = try der_reader.readByte();
604 bytes_read.* += 1;
605 return try parse_value_with_tag_byte(tag_byte, alloc, bytes_read, der_reader);
606 }
607
608 pub fn parse_value(alloc: *Allocator, der_reader: anytype) DecodeError(@TypeOf(der_reader))!Value {
609 var read: usize = 0;
610 return try parse_value_internal(alloc, &read, der_reader);
611 }
612};
613
614test "der.parse_value" {
615 const github_der = @embedFile("../test/github.der");
616 var fbs = std.io.fixedBufferStream(github_der);
617
618 var arena = ArenaAllocator.init(std.testing.allocator);
619 defer arena.deinit();
620
621 _ = try der.parse_value(&arena.allocator, fbs.reader());
622}
libs/iguanatls/src/ciphersuites.zig created+534
...@@ -0,0 +1,534 @@
1const std = @import("std");
2const mem = std.mem;
3
4usingnamespace @import("crypto.zig");
5const Chacha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305;
6const Aes128Gcm = std.crypto.aead.aes_gcm.Aes128Gcm;
7const record_length = @import("main.zig").record_length;
8
9pub const suites = struct {
10 pub const ECDHE_RSA_Chacha20_Poly1305 = struct {
11 pub const name = "ECDHE-RSA-CHACHA20-POLY1305";
12 pub const tag = 0xCCA8;
13 pub const key_exchange = .ecdhe;
14 pub const hash = .sha256;
15
16 pub const Keys = struct {
17 client_key: [32]u8,
18 server_key: [32]u8,
19 client_iv: [12]u8,
20 server_iv: [12]u8,
21 };
22
23 pub const State = union(enum) {
24 none,
25 in_record: struct {
26 left: usize,
27 context: ChaCha20Stream.BlockVec,
28 idx: usize,
29 buf: [64]u8,
30 },
31 };
32 pub const default_state: State = .none;
33
34 pub fn raw_write(
35 comptime buffer_size: usize,
36 rand: *std.rand.Random,
37 key_data: anytype,
38 writer: anytype,
39 prefix: [3]u8,
40 seq: u64,
41 buffer: []const u8,
42 ) !void {
43 std.debug.assert(buffer.len <= buffer_size);
44 try writer.writeAll(&prefix);
45 try writer.writeIntBig(u16, @intCast(u16, buffer.len + 16));
46
47 var additional_data: [13]u8 = undefined;
48 mem.writeIntBig(u64, additional_data[0..8], seq);
49 additional_data[8..11].* = prefix;
50 mem.writeIntBig(u16, additional_data[11..13], @intCast(u16, buffer.len));
51
52 var encrypted_data: [buffer_size]u8 = undefined;
53 var tag_data: [16]u8 = undefined;
54
55 var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8);
56 mem.writeIntBig(u64, nonce[4..12], seq);
57 for (nonce) |*n, i| {
58 n.* ^= key_data.client_iv(@This())[i];
59 }
60
61 Chacha20Poly1305.encrypt(
62 encrypted_data[0..buffer.len],
63 &tag_data,
64 buffer,
65 &additional_data,
66 nonce,
67 key_data.client_key(@This()).*,
68 );
69 try writer.writeAll(encrypted_data[0..buffer.len]);
70 try writer.writeAll(&tag_data);
71 }
72
73 pub fn check_verify_message(
74 key_data: anytype,
75 length: usize,
76 reader: anytype,
77 verify_message: [16]u8,
78 ) !bool {
79 if (length != 32)
80 return false;
81
82 var msg_in: [32]u8 = undefined;
83 try reader.readNoEof(&msg_in);
84
85 const additional_data: [13]u8 = ([1]u8{0} ** 8) ++ [5]u8{ 0x16, 0x03, 0x03, 0x00, 0x10 };
86 var decrypted: [16]u8 = undefined;
87 Chacha20Poly1305.decrypt(
88 &decrypted,
89 msg_in[0..16],
90 msg_in[16..].*,
91 &additional_data,
92 key_data.server_iv(@This()).*,
93 key_data.server_key(@This()).*,
94 ) catch return false;
95
96 return mem.eql(u8, &decrypted, &verify_message);
97 }
98
99 pub fn read(
100 comptime buf_size: usize,
101 state: *State,
102 key_data: anytype,
103 reader: anytype,
104 server_seq: *u64,
105 buffer: []u8,
106 ) !usize {
107 switch (state.*) {
108 .none => {
109 const len = (record_length(0x17, reader) catch |err| switch (err) {
110 error.EndOfStream => return 0,
111 else => |e| return e,
112 }) - 16;
113
114 const curr_bytes = std.math.min(std.math.min(len, buf_size), buffer.len);
115
116 var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8);
117 mem.writeIntBig(u64, nonce[4..12], server_seq.*);
118 for (nonce) |*n, i| {
119 n.* ^= key_data.server_iv(@This())[i];
120 }
121
122 // Partially decrypt the data.
123 var encrypted: [buf_size]u8 = undefined;
124 const actually_read = try reader.read(encrypted[0..curr_bytes]);
125
126 var c: [4]u32 = undefined;
127 c[0] = 1;
128 c[1] = mem.readIntLittle(u32, nonce[0..4]);
129 c[2] = mem.readIntLittle(u32, nonce[4..8]);
130 c[3] = mem.readIntLittle(u32, nonce[8..12]);
131 const server_key = keyToWords(key_data.server_key(@This()).*);
132 var context = ChaCha20Stream.initContext(server_key, c);
133 var idx: usize = 0;
134 var buf: [64]u8 = undefined;
135 ChaCha20Stream.chacha20Xor(
136 buffer[0..actually_read],
137 encrypted[0..actually_read],
138 server_key,
139 &context,
140 &idx,
141 &buf,
142 );
143 if (actually_read < len) {
144 state.* = .{
145 .in_record = .{
146 .left = len - actually_read,
147 .context = context,
148 .idx = idx,
149 .buf = buf,
150 },
151 };
152 } else {
153 // @TODO Verify Poly1305.
154 reader.skipBytes(16, .{}) catch |err| switch (err) {
155 error.EndOfStream => return 0,
156 else => |e| return e,
157 };
158 server_seq.* += 1;
159 }
160 return actually_read;
161 },
162 .in_record => |*record_info| {
163 const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);
164 // Partially decrypt the data.
165 var encrypted: [buf_size]u8 = undefined;
166 const actually_read = try reader.read(encrypted[0..curr_bytes]);
167 ChaCha20Stream.chacha20Xor(
168 buffer[0..actually_read],
169 encrypted[0..actually_read],
170 keyToWords(key_data.server_key(@This()).*),
171 &record_info.context,
172 &record_info.idx,
173 &record_info.buf,
174 );
175
176 record_info.left -= actually_read;
177 if (record_info.left == 0) {
178 // @TODO Verify Poly1305.
179 reader.skipBytes(16, .{}) catch |err| switch (err) {
180 error.EndOfStream => return 0,
181 else => |e| return e,
182 };
183 state.* = .none;
184 server_seq.* += 1;
185 }
186 return actually_read;
187 },
188 }
189 }
190 };
191
192 pub const ECDHE_RSA_AES128_GCM_SHA256 = struct {
193 pub const name = "ECDHE-RSA-AES128-GCM-SHA256";
194 pub const tag = 0xC02F;
195 pub const key_exchange = .ecdhe;
196 pub const hash = .sha256;
197
198 pub const Keys = struct {
199 client_key: [16]u8,
200 server_key: [16]u8,
201 client_iv: [4]u8,
202 server_iv: [4]u8,
203 };
204
205 const Aes = std.crypto.core.aes.Aes128;
206 pub const State = union(enum) {
207 none,
208 in_record: struct {
209 left: usize,
210 aes: @typeInfo(@TypeOf(Aes.initEnc)).Fn.return_type.?,
211 // ctr state
212 counterInt: u128,
213 idx: usize,
214 },
215 };
216 pub const default_state: State = .none;
217
218 pub fn check_verify_message(
219 key_data: anytype,
220 length: usize,
221 reader: anytype,
222 verify_message: [16]u8,
223 ) !bool {
224 if (length != 40)
225 return false;
226
227 var iv: [12]u8 = undefined;
228 iv[0..4].* = key_data.server_iv(@This()).*;
229 try reader.readNoEof(iv[4..12]);
230
231 var msg_in: [32]u8 = undefined;
232 try reader.readNoEof(&msg_in);
233
234 const additional_data: [13]u8 = ([1]u8{0} ** 8) ++ [5]u8{ 0x16, 0x03, 0x03, 0x00, 0x10 };
235 var decrypted: [16]u8 = undefined;
236 Aes128Gcm.decrypt(
237 &decrypted,
238 msg_in[0..16],
239 msg_in[16..].*,
240 &additional_data,
241 iv,
242 key_data.server_key(@This()).*,
243 ) catch return false;
244
245 return mem.eql(u8, &decrypted, &verify_message);
246 }
247
248 pub fn raw_write(
249 comptime buffer_size: usize,
250 rand: *std.rand.Random,
251 key_data: anytype,
252 writer: anytype,
253 prefix: [3]u8,
254 seq: u64,
255 buffer: []const u8,
256 ) !void {
257 std.debug.assert(buffer.len <= buffer_size);
258 var iv: [12]u8 = undefined;
259 iv[0..4].* = key_data.client_iv(@This()).*;
260 rand.bytes(iv[4..12]);
261
262 var additional_data: [13]u8 = undefined;
263 mem.writeIntBig(u64, additional_data[0..8], seq);
264 additional_data[8..11].* = prefix;
265 mem.writeIntBig(u16, additional_data[11..13], @intCast(u16, buffer.len));
266
267 try writer.writeAll(&prefix);
268 try writer.writeIntBig(u16, @intCast(u16, buffer.len + 24));
269 try writer.writeAll(iv[4..12]);
270
271 var encrypted_data: [buffer_size]u8 = undefined;
272 var tag_data: [16]u8 = undefined;
273
274 Aes128Gcm.encrypt(
275 encrypted_data[0..buffer.len],
276 &tag_data,
277 buffer,
278 &additional_data,
279 iv,
280 key_data.client_key(@This()).*,
281 );
282 try writer.writeAll(encrypted_data[0..buffer.len]);
283 try writer.writeAll(&tag_data);
284 }
285
286 pub fn read(
287 comptime buf_size: usize,
288 state: *State,
289 key_data: anytype,
290 reader: anytype,
291 server_seq: *u64,
292 buffer: []u8,
293 ) !usize {
294 switch (state.*) {
295 .none => {
296 const len = (record_length(0x17, reader) catch |err| switch (err) {
297 error.EndOfStream => return 0,
298 else => |e| return e,
299 }) - 24;
300
301 const curr_bytes = std.math.min(std.math.min(len, buf_size), buffer.len);
302
303 var iv: [12]u8 = undefined;
304 iv[0..4].* = key_data.server_iv(@This()).*;
305 reader.readNoEof(iv[4..12]) catch |err| switch (err) {
306 error.EndOfStream => return 0,
307 else => |e| return e,
308 };
309
310 // Partially decrypt the data.
311 var encrypted: [buf_size]u8 = undefined;
312 const actually_read = try reader.read(encrypted[0..curr_bytes]);
313
314 const aes = Aes.initEnc(key_data.server_key(@This()).*);
315
316 var j: [16]u8 = undefined;
317 mem.copy(u8, j[0..12], iv[0..]);
318 mem.writeIntBig(u32, j[12..][0..4], 2);
319
320 var counterInt = mem.readInt(u128, &j, .Big);
321 var idx: usize = 0;
322
323 ctr(
324 @TypeOf(aes),
325 aes,
326 buffer[0..actually_read],
327 encrypted[0..actually_read],
328 &counterInt,
329 &idx,
330 .Big,
331 );
332
333 if (actually_read < len) {
334 state.* = .{
335 .in_record = .{
336 .left = len - actually_read,
337 .aes = aes,
338 .counterInt = counterInt,
339 .idx = idx,
340 },
341 };
342 } else {
343 // @TODO Verify the message
344 reader.skipBytes(16, .{}) catch |err| switch (err) {
345 error.EndOfStream => return 0,
346 else => |e| return e,
347 };
348 server_seq.* += 1;
349 }
350 return actually_read;
351 },
352 .in_record => |*record_info| {
353 const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);
354 // Partially decrypt the data.
355 var encrypted: [buf_size]u8 = undefined;
356 const actually_read = try reader.read(encrypted[0..curr_bytes]);
357
358 ctr(
359 @TypeOf(record_info.aes),
360 record_info.aes,
361 buffer[0..actually_read],
362 encrypted[0..actually_read],
363 &record_info.counterInt,
364 &record_info.idx,
365 .Big,
366 );
367 record_info.left -= actually_read;
368 if (record_info.left == 0) {
369 // @TODO Verify Poly1305.
370 reader.skipBytes(16, .{}) catch |err| switch (err) {
371 error.EndOfStream => return 0,
372 else => |e| return e,
373 };
374 state.* = .none;
375 server_seq.* += 1;
376 }
377 return actually_read;
378 },
379 }
380 }
381 };
382
383 pub const all = &[_]type{ ECDHE_RSA_Chacha20_Poly1305, ECDHE_RSA_AES128_GCM_SHA256 };
384};
385
386fn key_field_width(comptime T: type, comptime field: anytype) ?usize {
387 if (!@hasField(T, @tagName(field)))
388 return null;
389
390 const field_info = std.meta.fieldInfo(T, field);
391 if (!comptime std.meta.trait.is(.Array)(field_info.field_type) or std.meta.Elem(field_info.field_type) != u8)
392 @compileError("Field '" ++ field ++ "' of type '" ++ @typeName(T) ++ "' should be an array of u8.");
393
394 return @typeInfo(field_info.field_type).Array.len;
395}
396
397pub fn key_data_size(comptime ciphersuites: []const type) usize {
398 var max: usize = 0;
399 for (ciphersuites) |cs| {
400 const curr = (key_field_width(cs.Keys, .client_mac) orelse 0) +
401 (key_field_width(cs.Keys, .server_mac) orelse 0) +
402 key_field_width(cs.Keys, .client_key).? +
403 key_field_width(cs.Keys, .server_key).? +
404 key_field_width(cs.Keys, .client_iv).? +
405 key_field_width(cs.Keys, .server_iv).?;
406 if (curr > max)
407 max = curr;
408 }
409 return max;
410}
411
412pub fn KeyData(comptime ciphersuites: []const type) type {
413 return struct {
414 data: [key_data_size(ciphersuites)]u8,
415
416 pub fn client_mac(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_mac) orelse 0]u8 {
417 return self.data[0..comptime (key_field_width(cs.Keys, .client_mac) orelse 0)];
418 }
419
420 pub fn server_mac(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_mac) orelse 0]u8 {
421 const start = key_field_width(cs.Keys, .client_mac) orelse 0;
422 return self.data[start..][0..comptime (key_field_width(cs.Keys, .server_mac) orelse 0)];
423 }
424
425 pub fn client_key(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_key).?]u8 {
426 const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
427 (key_field_width(cs.Keys, .server_mac) orelse 0);
428 return self.data[start..][0..comptime key_field_width(cs.Keys, .client_key).?];
429 }
430
431 pub fn server_key(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_key).?]u8 {
432 const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
433 (key_field_width(cs.Keys, .server_mac) orelse 0) +
434 key_field_width(cs.Keys, .client_key).?;
435 return self.data[start..][0..comptime key_field_width(cs.Keys, .server_key).?];
436 }
437
438 pub fn client_iv(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_iv).?]u8 {
439 const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
440 (key_field_width(cs.Keys, .server_mac) orelse 0) +
441 key_field_width(cs.Keys, .client_key).? +
442 key_field_width(cs.Keys, .server_key).?;
443 return self.data[start..][0..comptime key_field_width(cs.Keys, .client_iv).?];
444 }
445
446 pub fn server_iv(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_iv).?]u8 {
447 const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
448 (key_field_width(cs.Keys, .server_mac) orelse 0) +
449 key_field_width(cs.Keys, .client_key).? +
450 key_field_width(cs.Keys, .server_key).? +
451 key_field_width(cs.Keys, .client_iv).?;
452 return self.data[start..][0..comptime key_field_width(cs.Keys, .server_iv).?];
453 }
454 };
455}
456
457pub fn key_expansion(
458 comptime ciphersuites: []const type,
459 tag: u16,
460 context: anytype,
461 comptime next_32_bytes: anytype,
462) KeyData(ciphersuites) {
463 var res: KeyData(ciphersuites) = undefined;
464 inline for (ciphersuites) |cs| {
465 if (cs.tag == tag) {
466 var chunk: [32]u8 = undefined;
467 next_32_bytes(context, 0, &chunk);
468 comptime var chunk_idx = 1;
469 comptime var data_cursor = 0;
470 comptime var chunk_cursor = 0;
471
472 const fields = .{
473 .client_mac, .server_mac,
474 .client_key, .server_key,
475 .client_iv, .server_iv,
476 };
477 inline for (fields) |field| {
478 if (chunk_cursor == 32) {
479 next_32_bytes(context, chunk_idx, &chunk);
480 chunk_idx += 1;
481 chunk_cursor = 0;
482 }
483
484 const field_width = comptime (key_field_width(cs.Keys, field) orelse 0);
485 const first_read = comptime std.math.min(32 - chunk_cursor, field_width);
486 const second_read = field_width - first_read;
487
488 res.data[data_cursor..][0..first_read].* = chunk[chunk_cursor..][0..first_read].*;
489 data_cursor += first_read;
490 chunk_cursor += first_read;
491
492 if (second_read != 0) {
493 next_32_bytes(context, chunk_idx, &chunk);
494 chunk_idx += 1;
495 res.data[data_cursor..][0..second_read].* = chunk[chunk_cursor..][0..second_read].*;
496 data_cursor += second_read;
497 chunk_cursor = second_read;
498 comptime std.debug.assert(chunk_cursor != 32);
499 }
500 }
501
502 return res;
503 }
504 }
505 unreachable;
506}
507
508pub fn ClientState(comptime ciphersuites: []const type) type {
509 var fields: [ciphersuites.len]std.builtin.TypeInfo.UnionField = undefined;
510 for (ciphersuites) |cs, i| {
511 fields[i] = .{
512 .name = cs.name,
513 .field_type = cs.State,
514 .alignment = if (@sizeOf(cs.State) > 0) @alignOf(cs.State) else 0,
515 };
516 }
517 return @Type(.{
518 .Union = .{
519 .layout = .Extern,
520 .tag_type = null,
521 .fields = &fields,
522 .decls = &[0]std.builtin.TypeInfo.Declaration{},
523 },
524 });
525}
526
527pub fn client_state_default(comptime ciphersuites: []const type, tag: u16) ClientState(ciphersuites) {
528 inline for (ciphersuites) |cs| {
529 if (cs.tag == tag) {
530 return @unionInit(ClientState(ciphersuites), cs.name, cs.default_state);
531 }
532 }
533 unreachable;
534}
libs/iguanatls/src/crypto.zig created+936
...@@ -0,0 +1,936 @@
1const std = @import("std");
2const mem = std.mem;
3
4// TODO See stdlib, this is a modified non vectorized implementation
5pub const ChaCha20Stream = struct {
6 const math = std.math;
7 pub const BlockVec = [16]u32;
8
9 pub fn initContext(key: [8]u32, d: [4]u32) BlockVec {
10 const c = "expand 32-byte k";
11 const constant_le = comptime [4]u32{
12 mem.readIntLittle(u32, c[0..4]),
13 mem.readIntLittle(u32, c[4..8]),
14 mem.readIntLittle(u32, c[8..12]),
15 mem.readIntLittle(u32, c[12..16]),
16 };
17 return BlockVec{
18 constant_le[0], constant_le[1], constant_le[2], constant_le[3],
19 key[0], key[1], key[2], key[3],
20 key[4], key[5], key[6], key[7],
21 d[0], d[1], d[2], d[3],
22 };
23 }
24
25 const QuarterRound = struct {
26 a: usize,
27 b: usize,
28 c: usize,
29 d: usize,
30 };
31
32 fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
33 return QuarterRound{
34 .a = a,
35 .b = b,
36 .c = c,
37 .d = d,
38 };
39 }
40
41 inline fn chacha20Core(x: *BlockVec, input: BlockVec) void {
42 x.* = input;
43
44 const rounds = comptime [_]QuarterRound{
45 Rp(0, 4, 8, 12),
46 Rp(1, 5, 9, 13),
47 Rp(2, 6, 10, 14),
48 Rp(3, 7, 11, 15),
49 Rp(0, 5, 10, 15),
50 Rp(1, 6, 11, 12),
51 Rp(2, 7, 8, 13),
52 Rp(3, 4, 9, 14),
53 };
54
55 comptime var j: usize = 0;
56 inline while (j < 20) : (j += 2) {
57 inline for (rounds) |r| {
58 x[r.a] +%= x[r.b];
59 x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16));
60 x[r.c] +%= x[r.d];
61 x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12));
62 x[r.a] +%= x[r.b];
63 x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8));
64 x[r.c] +%= x[r.d];
65 x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));
66 }
67 }
68 }
69
70 inline fn hashToBytes(out: *[64]u8, x: BlockVec) void {
71 var i: usize = 0;
72 while (i < 4) : (i += 1) {
73 mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]);
74 mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]);
75 mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]);
76 mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]);
77 }
78 }
79
80 inline fn contextFeedback(x: *BlockVec, ctx: BlockVec) void {
81 var i: usize = 0;
82 while (i < 16) : (i += 1) {
83 x[i] +%= ctx[i];
84 }
85 }
86
87 // TODO: Optimize this
88 pub fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, ctx: *BlockVec, idx: *usize, buf: *[64]u8) void {
89 var x: BlockVec = undefined;
90
91 const start_idx = idx.*;
92 var i: usize = 0;
93 while (i < in.len) {
94 if (idx.* % 64 == 0) {
95 if (idx.* != 0) {
96 ctx.*[12] += 1;
97 }
98 chacha20Core(x[0..], ctx.*);
99 contextFeedback(&x, ctx.*);
100 hashToBytes(buf, x);
101 }
102
103 out[i] = in[i] ^ buf[idx.* % 64];
104
105 i += 1;
106 idx.* += 1;
107 }
108 }
109};
110
111pub fn keyToWords(key: [32]u8) [8]u32 {
112 var k: [8]u32 = undefined;
113 var i: usize = 0;
114 while (i < 8) : (i += 1) {
115 k[i] = mem.readIntLittle(u32, key[i * 4 ..][0..4]);
116 }
117 return k;
118}
119
120// See std.crypto.core.modes.ctr
121/// This mode creates a key stream by encrypting an incrementing counter using a block cipher, and adding it to the source material.
122pub fn ctr(
123 comptime BlockCipher: anytype,
124 block_cipher: BlockCipher,
125 dst: []u8,
126 src: []const u8,
127 counterInt: *u128,
128 idx: *usize,
129 endian: comptime std.builtin.Endian,
130) void {
131 std.debug.assert(dst.len >= src.len);
132 const block_length = BlockCipher.block_length;
133 var cur_idx: usize = 0;
134
135 const offset = idx.* % block_length;
136 if (offset != 0) {
137 const part_len = std.math.min(block_length - offset, src.len);
138
139 var counter: [BlockCipher.block_length]u8 = undefined;
140 mem.writeInt(u128, &counter, counterInt.*, endian);
141 var pad = [_]u8{0} ** block_length;
142 mem.copy(u8, pad[offset..], src[0..part_len]);
143 block_cipher.xor(&pad, &pad, counter);
144 mem.copy(u8, dst[0..part_len], pad[offset..][0..part_len]);
145
146 cur_idx += part_len;
147 idx.* += part_len;
148 if (idx.* % block_length == 0)
149 counterInt.* += 1;
150 }
151
152 const start_idx = cur_idx;
153 const remaining = src.len - cur_idx;
154 cur_idx = 0;
155
156 const parallel_count = BlockCipher.block.parallel.optimal_parallel_blocks;
157 const wide_block_length = parallel_count * 16;
158 if (remaining >= wide_block_length) {
159 var counters: [parallel_count * 16]u8 = undefined;
160 while (cur_idx + wide_block_length <= remaining) : (cur_idx += wide_block_length) {
161 comptime var j = 0;
162 inline while (j < parallel_count) : (j += 1) {
163 mem.writeInt(u128, counters[j * 16 .. j * 16 + 16], counterInt.*, endian);
164 counterInt.* +%= 1;
165 }
166 block_cipher.xorWide(parallel_count, dst[start_idx..][cur_idx .. cur_idx + wide_block_length][0..wide_block_length], src[start_idx..][cur_idx .. cur_idx + wide_block_length][0..wide_block_length], counters);
167 idx.* += wide_block_length;
168 }
169 }
170 while (cur_idx + block_length <= remaining) : (cur_idx += block_length) {
171 var counter: [BlockCipher.block_length]u8 = undefined;
172 mem.writeInt(u128, &counter, counterInt.*, endian);
173 counterInt.* +%= 1;
174 block_cipher.xor(dst[start_idx..][cur_idx .. cur_idx + block_length][0..block_length], src[start_idx..][cur_idx .. cur_idx + block_length][0..block_length], counter);
175 idx.* += block_length;
176 }
177 if (cur_idx < remaining) {
178 std.debug.assert(idx.* % block_length == 0);
179 var counter: [BlockCipher.block_length]u8 = undefined;
180 mem.writeInt(u128, &counter, counterInt.*, endian);
181
182 var pad = [_]u8{0} ** block_length;
183 mem.copy(u8, &pad, src[start_idx..][cur_idx..]);
184 block_cipher.xor(&pad, &pad, counter);
185 mem.copy(u8, dst[cur_idx..], pad[0 .. remaining - cur_idx]);
186
187 idx.* += remaining - cur_idx;
188 if (idx.* % block_length == 0)
189 counterInt.* +%= 1;
190 }
191}
192
193// Ported from BearSSL's ec_prime_i31 engine
194pub const ecc = struct {
195 pub const SECP384R1 = struct {
196 pub const point_len = 96;
197
198 const order = [point_len / 2]u8{
199 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
200 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
201 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
202 0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF,
203 0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A,
204 0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73,
205 };
206
207 const P = [_]u32{
208 0x0000018C, 0x7FFFFFFF, 0x00000001, 0x00000000,
209 0x7FFFFFF8, 0x7FFFFFEF, 0x7FFFFFFF, 0x7FFFFFFF,
210 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF,
211 0x7FFFFFFF, 0x00000FFF,
212 };
213 const R2 = [_]u32{
214 0x0000018C, 0x00000000, 0x00000080, 0x7FFFFE00,
215 0x000001FF, 0x00000800, 0x00000000, 0x7FFFE000,
216 0x00001FFF, 0x00008000, 0x00008000, 0x00000000,
217 0x00000000, 0x00000000,
218 };
219 const B = [_]u32{
220 0x0000018C, 0x6E666840, 0x070D0392, 0x5D810231,
221 0x7651D50C, 0x17E218D6, 0x1B192002, 0x44EFE441,
222 0x3A524E2B, 0x2719BA5F, 0x41F02209, 0x36C5643E,
223 0x5813EFFE, 0x000008A5,
224 };
225
226 const base_point = [point_len]u8{
227 0xAA, 0x87, 0xCA, 0x22, 0xBE, 0x8B, 0x05, 0x37,
228 0x8E, 0xB1, 0xC7, 0x1E, 0xF3, 0x20, 0xAD, 0x74,
229 0x6E, 0x1D, 0x3B, 0x62, 0x8B, 0xA7, 0x9B, 0x98,
230 0x59, 0xF7, 0x41, 0xE0, 0x82, 0x54, 0x2A, 0x38,
231 0x55, 0x02, 0xF2, 0x5D, 0xBF, 0x55, 0x29, 0x6C,
232 0x3A, 0x54, 0x5E, 0x38, 0x72, 0x76, 0x0A, 0xB7,
233 0x36, 0x17, 0xDE, 0x4A, 0x96, 0x26, 0x2C, 0x6F,
234 0x5D, 0x9E, 0x98, 0xBF, 0x92, 0x92, 0xDC, 0x29,
235 0xF8, 0xF4, 0x1D, 0xBD, 0x28, 0x9A, 0x14, 0x7C,
236 0xE9, 0xDA, 0x31, 0x13, 0xB5, 0xF0, 0xB8, 0xC0,
237 0x0A, 0x60, 0xB1, 0xCE, 0x1D, 0x7E, 0x81, 0x9D,
238 0x7A, 0x43, 0x1D, 0x7C, 0x90, 0xEA, 0x0E, 0x5F,
239 };
240
241 comptime {
242 std.debug.assert((P[0] - (P[0] >> 5) + 7) >> 2 == point_len + 1);
243 }
244 };
245
246 fn jacobian_len(comptime Curve: type) usize {
247 return @divTrunc(Curve.order.len * 8 + 61, 31);
248 }
249
250 fn Jacobian(comptime Curve: type) type {
251 return [3][jacobian_len(Curve)]u32;
252 }
253
254 fn zero_jacobian(comptime Curve: type) Jacobian(Curve) {
255 var result = std.mem.zeroes(Jacobian(Curve));
256 result[0][0] = Curve.P[0];
257 result[1][0] = Curve.P[0];
258 result[2][0] = Curve.P[0];
259 return result;
260 }
261
262 pub fn scalarmult(
263 comptime Curve: type,
264 point: [Curve.point_len]u8,
265 k: []const u8,
266 ) ![Curve.point_len]u8 {
267 var P: Jacobian(Curve) = undefined;
268 var res: u32 = decode_to_jacobian(Curve, &P, point);
269 point_mul(Curve, &P, k);
270 var out: [Curve.point_len]u8 = undefined;
271 encode_from_jacobian(Curve, &out, P);
272 if (res == 0)
273 return error.MultiplicationFailed;
274 return out;
275 }
276
277 pub fn KeyPair(comptime Curve: type) type {
278 return struct {
279 public_key: [Curve.point_len]u8,
280 secret_key: [Curve.point_len / 2]u8,
281 };
282 }
283
284 pub fn make_key_pair(comptime Curve: type, rand_bytes: [Curve.point_len / 2]u8) KeyPair(Curve) {
285 var key_bytes = rand_bytes;
286 comptime var mask: u8 = 0xFF;
287 comptime {
288 while (mask >= Curve.order[0]) {
289 mask >>= 1;
290 }
291 }
292 key_bytes[0] &= mask;
293 key_bytes[Curve.point_len / 2 - 1] |= 0x01;
294
295 return .{
296 .secret_key = key_bytes,
297 .public_key = scalarmult(Curve, Curve.base_point, &key_bytes) catch unreachable,
298 };
299 }
300
301 fn jacobian_with_one_set(comptime Curve: type, comptime fields: [2][jacobian_len(Curve)]u32) Jacobian(Curve) {
302 comptime const plen = (Curve.P[0] + 63) >> 5;
303 return fields ++ [1][jacobian_len(Curve)]u32{
304 [2]u32{ Curve.P[0], 1 } ++ ([1]u32{0} ** (plen - 2)),
305 };
306 }
307
308 fn encode_from_jacobian(comptime Curve: type, point: *[Curve.point_len]u8, P: Jacobian(Curve)) void {
309 var Q = P;
310 const T = comptime jacobian_with_one_set(Curve, [2][jacobian_len(Curve)]u32{ undefined, undefined });
311 _ = run_code(Curve, &Q, T, &code.affine);
312 encode_jacobian_part(Curve, point[0 .. Curve.point_len / 2], Q[0]);
313 encode_jacobian_part(Curve, point[Curve.point_len / 2 ..], Q[1]);
314 }
315
316 fn point_mul(comptime Curve: type, P: *Jacobian(Curve), x: []const u8) void {
317 var P2 = P.*;
318 point_double(Curve, &P2);
319 var P3 = P.*;
320 point_add(Curve, &P3, P2);
321 var Q = zero_jacobian(Curve);
322 var qz: u32 = 1;
323 var xlen = x.len;
324 var xidx: usize = 0;
325 while (xlen > 0) : ({
326 xlen -= 1;
327 xidx += 1;
328 }) {
329 var k: u3 = 6;
330 while (true) : (k -= 2) {
331 point_double(Curve, &Q);
332 point_double(Curve, &Q);
333 var T = P.*;
334 var U = Q;
335 const bits = @as(u32, x[xidx] >> k) & 3;
336 const bnz = NEQ(bits, 0);
337 CCOPY(EQ(bits, 2), mem.asBytes(&T), mem.asBytes(&P2));
338 CCOPY(EQ(bits, 3), mem.asBytes(&T), mem.asBytes(&P3));
339 point_add(Curve, &U, T);
340 CCOPY(bnz & qz, mem.asBytes(&Q), mem.asBytes(&T));
341 CCOPY(bnz & ~qz, mem.asBytes(&Q), mem.asBytes(&U));
342 qz &= ~bnz;
343
344 if (k == 0)
345 break;
346 }
347 }
348 P.* = Q;
349 }
350
351 inline fn point_double(comptime Curve: type, P: *Jacobian(Curve)) void {
352 _ = run_code(Curve, P, P.*, &code.double);
353 }
354 inline fn point_add(comptime Curve: type, P1: *Jacobian(Curve), P2: Jacobian(Curve)) void {
355 _ = run_code(Curve, P1, P2, &code._add);
356 }
357
358 fn decode_to_jacobian(
359 comptime Curve: type,
360 out: *Jacobian(Curve),
361 point: [Curve.point_len]u8,
362 ) u32 {
363 out.* = zero_jacobian(Curve);
364 var result = decode_mod(Curve, &out.*[0], point[0 .. Curve.point_len / 2].*);
365 result &= decode_mod(Curve, &out.*[1], point[Curve.point_len / 2 ..].*);
366
367 const zlen = comptime ((Curve.P[0] + 63) >> 5);
368 comptime std.debug.assert(zlen == @typeInfo(@TypeOf(Curve.R2)).Array.len);
369 comptime std.debug.assert(zlen == @typeInfo(@TypeOf(Curve.B)).Array.len);
370
371 const Q = comptime jacobian_with_one_set(Curve, [2][jacobian_len(Curve)]u32{ Curve.R2, Curve.B });
372 result &= ~run_code(Curve, out, Q, &code.check);
373 return result;
374 }
375
376 const code = struct {
377 const P1x = 0;
378 const P1y = 1;
379 const P1z = 2;
380 const P2x = 3;
381 const P2y = 4;
382 const P2z = 5;
383 const Px = 0;
384 const Py = 1;
385 const Pz = 2;
386 const t1 = 6;
387 const t2 = 7;
388 const t3 = 8;
389 const t4 = 9;
390 const t5 = 10;
391 const t6 = 11;
392 const t7 = 12;
393 const t8 = 3;
394 const t9 = 4;
395 const t10 = 5;
396 fn MSET(comptime d: u16, comptime a: u16) u16 {
397 return 0x0000 + (d << 8) + (a << 4);
398 }
399 fn MADD(comptime d: u16, comptime a: u16) u16 {
400 return 0x1000 + (d << 8) + (a << 4);
401 }
402 fn MSUB(comptime d: u16, comptime a: u16) u16 {
403 return 0x2000 + (d << 8) + (a << 4);
404 }
405 fn MMUL(comptime d: u16, comptime a: u16, comptime b: u16) u16 {
406 return 0x3000 + (d << 8) + (a << 4) + b;
407 }
408 fn MINV(comptime d: u16, comptime a: u16, comptime b: u16) u16 {
409 return 0x4000 + (d << 8) + (a << 4) + b;
410 }
411 fn MTZ(comptime d: u16) u16 {
412 return 0x5000 + (d << 8);
413 }
414 const ENDCODE = 0;
415
416 const check = [_]u16{
417 // Convert x and y to Montgomery representation.
418 MMUL(t1, P1x, P2x),
419 MMUL(t2, P1y, P2x),
420 MSET(P1x, t1),
421 MSET(P1y, t2),
422 // Compute x^3 in t1.
423 MMUL(t2, P1x, P1x),
424 MMUL(t1, P1x, t2),
425 // Subtract 3*x from t1.
426 MSUB(t1, P1x),
427 MSUB(t1, P1x),
428 MSUB(t1, P1x),
429 // Add b.
430 MADD(t1, P2y),
431 // Compute y^2 in t2.
432 MMUL(t2, P1y, P1y),
433 // Compare y^2 with x^3 - 3*x + b; they must match.
434 MSUB(t1, t2),
435 MTZ(t1),
436 // Set z to 1 (in Montgomery representation).
437 MMUL(P1z, P2x, P2z),
438 ENDCODE,
439 };
440 const double = [_]u16{
441 // Compute z^2 (in t1).
442 MMUL(t1, Pz, Pz),
443 // Compute x-z^2 (in t2) and then x+z^2 (in t1).
444 MSET(t2, Px),
445 MSUB(t2, t1),
446 MADD(t1, Px),
447 // Compute m = 3*(x+z^2)*(x-z^2) (in t1).
448 MMUL(t3, t1, t2),
449 MSET(t1, t3),
450 MADD(t1, t3),
451 MADD(t1, t3),
452 // Compute s = 4*x*y^2 (in t2) and 2*y^2 (in t3).
453 MMUL(t3, Py, Py),
454 MADD(t3, t3),
455 MMUL(t2, Px, t3),
456 MADD(t2, t2),
457 // Compute x' = m^2 - 2*s.
458 MMUL(Px, t1, t1),
459 MSUB(Px, t2),
460 MSUB(Px, t2),
461 // Compute z' = 2*y*z.
462 MMUL(t4, Py, Pz),
463 MSET(Pz, t4),
464 MADD(Pz, t4),
465 // Compute y' = m*(s - x') - 8*y^4. Note that we already have
466 // 2*y^2 in t3.
467 MSUB(t2, Px),
468 MMUL(Py, t1, t2),
469 MMUL(t4, t3, t3),
470 MSUB(Py, t4),
471 MSUB(Py, t4),
472 ENDCODE,
473 };
474 const _add = [_]u16{
475 // Compute u1 = x1*z2^2 (in t1) and s1 = y1*z2^3 (in t3).
476 MMUL(t3, P2z, P2z),
477 MMUL(t1, P1x, t3),
478 MMUL(t4, P2z, t3),
479 MMUL(t3, P1y, t4),
480 // Compute u2 = x2*z1^2 (in t2) and s2 = y2*z1^3 (in t4).
481 MMUL(t4, P1z, P1z),
482 MMUL(t2, P2x, t4),
483 MMUL(t5, P1z, t4),
484 MMUL(t4, P2y, t5),
485 //Compute h = u2 - u1 (in t2) and r = s2 - s1 (in t4).
486 MSUB(t2, t1),
487 MSUB(t4, t3),
488 // Report cases where r = 0 through the returned flag.
489 MTZ(t4),
490 // Compute u1*h^2 (in t6) and h^3 (in t5).
491 MMUL(t7, t2, t2),
492 MMUL(t6, t1, t7),
493 MMUL(t5, t7, t2),
494 // Compute x3 = r^2 - h^3 - 2*u1*h^2.
495 // t1 and t7 can be used as scratch registers.
496 MMUL(P1x, t4, t4),
497 MSUB(P1x, t5),
498 MSUB(P1x, t6),
499 MSUB(P1x, t6),
500 //Compute y3 = r*(u1*h^2 - x3) - s1*h^3.
501 MSUB(t6, P1x),
502 MMUL(P1y, t4, t6),
503 MMUL(t1, t5, t3),
504 MSUB(P1y, t1),
505 //Compute z3 = h*z1*z2.
506 MMUL(t1, P1z, P2z),
507 MMUL(P1z, t1, t2),
508 ENDCODE,
509 };
510 const affine = [_]u16{
511 // Save z*R in t1.
512 MSET(t1, P1z),
513 // Compute z^3 in t2.
514 MMUL(t2, P1z, P1z),
515 MMUL(t3, P1z, t2),
516 MMUL(t2, t3, P2z),
517 // Invert to (1/z^3) in t2.
518 MINV(t2, t3, t4),
519 // Compute y.
520 MSET(t3, P1y),
521 MMUL(P1y, t2, t3),
522 // Compute (1/z^2) in t3.
523 MMUL(t3, t2, t1),
524 // Compute x.
525 MSET(t2, P1x),
526 MMUL(P1x, t2, t3),
527 ENDCODE,
528 };
529 };
530
531 fn decode_mod(
532 comptime Curve: type,
533 x: *[jacobian_len(Curve)]u32,
534 src: [Curve.point_len / 2]u8,
535 ) u32 {
536 const mlen = comptime ((Curve.P[0] + 31) >> 5);
537 const tlen = comptime std.math.max(mlen << 2, Curve.point_len / 2) + 4;
538
539 var r: u32 = 0;
540 var pass: usize = 0;
541 while (pass < 2) : (pass += 1) {
542 var v: usize = 1;
543 var acc: u32 = 0;
544 var acc_len: u32 = 0;
545
546 var u: usize = 0;
547 while (u < tlen) : (u += 1) {
548 const b = if (u < Curve.point_len / 2)
549 @as(u32, src[Curve.point_len / 2 - 1 - u])
550 else
551 0;
552 acc |= b << @truncate(u5, acc_len);
553 acc_len += 8;
554 if (acc_len >= 31) {
555 const xw = acc & 0x7FFFFFFF;
556 acc_len -= 31;
557 acc = b >> @truncate(u5, 8 - acc_len);
558 if (v <= mlen) {
559 if (pass != 0) {
560 x[v] = r & xw;
561 } else {
562 const cc = @bitCast(u32, CMP(xw, Curve.P[v]));
563 r = MUX(EQ(cc, 0), r, cc);
564 }
565 } else if (pass == 0) {
566 r = MUX(EQ(xw, 0), r, 1);
567 }
568 v += 1;
569 }
570 }
571 r >>= 1;
572 r |= (r << 1);
573 }
574 x[0] = Curve.P[0];
575 return r & 1;
576 }
577
578 fn run_code(
579 comptime Curve: type,
580 P1: *Jacobian(Curve),
581 P2: Jacobian(Curve),
582 comptime Code: []const u16,
583 ) u32 {
584 comptime const jaclen = jacobian_len(Curve);
585
586 var t: [13][jaclen]u32 = undefined;
587 var result: u32 = 1;
588
589 t[0..3].* = P1.*;
590 t[3..6].* = P2;
591
592 comptime var u: usize = 0;
593 inline while (true) : (u += 1) {
594 comptime var op = Code[u];
595 if (op == 0)
596 break;
597 comptime const d = (op >> 8) & 0x0F;
598 comptime const a = (op >> 4) & 0x0F;
599 comptime const b = op & 0x0F;
600 op >>= 12;
601
602 switch (op) {
603 0 => t[d] = t[a],
604 1 => {
605 var ctl = add(jaclen, &t[d], t[a], 1);
606 ctl |= NOT(sub(jaclen, &t[d], Curve.P, 0));
607 _ = sub(jaclen, &t[d], Curve.P, ctl);
608 },
609 2 => _ = add(jaclen, &t[d], Curve.P, sub(jaclen, &t[d], t[a], 1)),
610 3 => montymul(Curve, &t[d], t[a], t[b], Curve.P, 1),
611 4 => {
612 var tp: [Curve.point_len / 2]u8 = undefined;
613 encode_jacobian_part(Curve, &tp, Curve.P);
614 tp[Curve.point_len / 2 - 1] -= 2;
615 modpow(Curve, &t[d], tp, 1, &t[a], &t[b]);
616 },
617 else => result &= ~iszero(jaclen, t[d]),
618 }
619 }
620 P1.* = t[0..3].*;
621 return result;
622 }
623
624 inline fn MUL31(x: u32, y: u32) u64 {
625 return @as(u64, x) * @as(u64, y);
626 }
627
628 inline fn MUL31_lo(x: u32, y: u32) u32 {
629 return (x *% y) & 0x7FFFFFFF;
630 }
631
632 inline fn MUX(ctl: u32, x: u32, y: u32) u32 {
633 return y ^ (@bitCast(u32, -@bitCast(i32, ctl)) & (x ^ y));
634 }
635 inline fn NOT(ctl: u32) u32 {
636 return ctl ^ 1;
637 }
638 inline fn NEQ(x: u32, y: u32) u32 {
639 const q = x ^ y;
640 return (q | @bitCast(u32, -@bitCast(i32, q))) >> 31;
641 }
642 inline fn EQ(x: u32, y: u32) u32 {
643 const q = x ^ y;
644 return NOT((q | @bitCast(u32, -@bitCast(i32, q))) >> 31);
645 }
646 inline fn CMP(x: u32, y: u32) i32 {
647 return @bitCast(i32, GT(x, y)) | -@bitCast(i32, GT(y, x));
648 }
649 inline fn GT(x: u32, y: u32) u32 {
650 const z = y -% x;
651 return (z ^ ((x ^ y) & (x ^ z))) >> 31;
652 }
653 inline fn LT(x: u32, y: u32) u32 {
654 return GT(y, x);
655 }
656 inline fn GE(x: u32, y: u32) u32 {
657 return NOT(GT(y, x));
658 }
659
660 fn CCOPY(ctl: u32, dst: []u8, src: []const u8) void {
661 for (src) |s, i| {
662 dst[i] = @truncate(u8, MUX(ctl, s, dst[i]));
663 }
664 }
665
666 // @TODO Remove lots of len and Curve parameters, just use the first byte calcualtions
667 // This will make all these functions shared for and reduce code bloat
668
669 inline fn set_zero(comptime len: usize, out: *[len]u32, bit_len: u32) void {
670 out[0] = bit_len;
671 mem.set(u32, out[1..][0 .. (bit_len + 31) >> 5], 0);
672 }
673
674 fn divrem(_hi: u32, _lo: u32, d: u32, r: *u32) u32 {
675 var hi = _hi;
676 var lo = _lo;
677 var q: u32 = 0;
678 const ch = EQ(hi, d);
679 hi = MUX(ch, 0, hi);
680
681 var k: u5 = 31;
682 while (k > 0) : (k -= 1) {
683 const j = @truncate(u5, 32 - @as(u6, k));
684 const w = (hi << j) | (lo >> k);
685 const ctl = GE(w, d) | (hi >> k);
686 const hi2 = (w -% d) >> j;
687 const lo2 = lo -% (d << k);
688 hi = MUX(ctl, hi2, hi);
689 lo = MUX(ctl, lo2, lo);
690 q |= ctl << k;
691 }
692 const cf = GE(lo, d) | hi;
693 q |= cf;
694 r.* = MUX(cf, lo -% d, lo);
695 return q;
696 }
697
698 inline fn div(hi: u32, lo: u32, d: u32) u32 {
699 var r: u32 = undefined;
700 return divrem(hi, lo, d, &r);
701 }
702
703 fn muladd_small(comptime len: usize, x: *[len]u32, z: u32, m: [len]u32) void {
704 var a0: u32 = undefined;
705 var a1: u32 = undefined;
706 var b0: u32 = undefined;
707 const mblr = @intCast(u5, m[0] & 31);
708 const mlen = (m[0] + 31) >> 5;
709 const hi = x[mlen];
710 if (mblr == 0) {
711 a0 = x[mlen];
712 mem.copyBackwards(u32, x[2..][0 .. mlen - 1], x[1..][0 .. mlen - 1]);
713 x[1] = z;
714 a1 = x[mlen];
715 b0 = m[mlen];
716 } else {
717 a0 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & 0x7FFFFFFF;
718 mem.copyBackwards(u32, x[2..][0 .. mlen - 1], x[1..][0 .. mlen - 1]);
719 x[1] = z;
720 a1 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & 0x7FFFFFFF;
721 b0 = ((m[mlen] << (31 - mblr)) | (m[mlen - 1] >> mblr)) & 0x7FFFFFFF;
722 }
723
724 const g = div(a0 >> 1, a1 | (a0 << 31), b0);
725 const q = MUX(EQ(a0, b0), 0x7FFFFFFF, MUX(EQ(g, 0), 0, g -% 1));
726
727 var cc: u32 = 0;
728 var tb: u32 = 1;
729 var u: usize = 1;
730 while (u <= mlen) : (u += 1) {
731 const mw = m[u];
732 const zl = MUL31(mw, q) + cc;
733 cc = @truncate(u32, zl >> 31);
734 const zw = @truncate(u32, zl) & 0x7FFFFFFF;
735 const xw = x[u];
736 var nxw = xw -% zw;
737 cc += nxw >> 31;
738 nxw &= 0x7FFFFFFF;
739 x[u] = nxw;
740 tb = MUX(EQ(nxw, mw), tb, GT(nxw, mw));
741 }
742
743 const over = GT(cc, hi);
744 const under = ~over & (tb | LT(cc, hi));
745 _ = add(len, x, m, over);
746 _ = sub(len, x, m, under);
747 }
748
749 fn to_monty(comptime len: usize, x: *[len]u32, m: [len]u32) void {
750 const mlen = (m[0] + 31) >> 5;
751 var k = mlen;
752 while (k > 0) : (k -= 1) {
753 muladd_small(len, x, 0, m);
754 }
755 }
756
757 fn modpow(
758 comptime Curve: type,
759 x: *[jacobian_len(Curve)]u32,
760 e: [Curve.point_len / 2]u8,
761 m0i: u32,
762 t1: *[jacobian_len(Curve)]u32,
763 t2: *[jacobian_len(Curve)]u32,
764 ) void {
765 comptime const jaclen = jacobian_len(Curve);
766 t1.* = x.*;
767 to_monty(jaclen, t1, Curve.P);
768 set_zero(jaclen, x, Curve.P[0]);
769 x[1] = 1;
770 comptime const bitlen = (Curve.point_len / 2) << 3;
771 var k: usize = 0;
772 while (k < bitlen) : (k += 1) {
773 const ctl = (e[Curve.point_len / 2 - 1 - (k >> 3)] >> (@truncate(u3, k & 7))) & 1;
774 montymul(Curve, t2, x.*, t1.*, Curve.P, m0i);
775 CCOPY(ctl, mem.asBytes(x), mem.asBytes(t2));
776 montymul(Curve, t2, t1.*, t1.*, Curve.P, m0i);
777 t1.* = t2.*;
778 }
779 }
780
781 fn encode_jacobian_part(comptime Curve: type, dst: *[Curve.point_len / 2]u8, x: [jacobian_len(Curve)]u32) void {
782 const xlen = (x[0] + 31) >> 5;
783
784 var buf = @ptrToInt(dst) + Curve.point_len / 2;
785 var len: usize = Curve.point_len / 2;
786 var k: usize = 1;
787 var acc: u32 = 0;
788 var acc_len: u5 = 0;
789 while (len != 0) {
790 const w = if (k <= xlen) x[k] else 0;
791 k += 1;
792 if (acc_len == 0) {
793 acc = w;
794 acc_len = 31;
795 } else {
796 const z = acc | (w << acc_len);
797 acc_len -= 1;
798 acc = w >> (31 - acc_len);
799 if (len >= 4) {
800 buf -= 4;
801 len -= 4;
802 mem.writeIntBig(u32, @intToPtr([*]u8, buf)[0..4], z);
803 } else {
804 switch (len) {
805 3 => {
806 @intToPtr(*u8, buf - 3).* = @truncate(u8, z >> 16);
807 @intToPtr(*u8, buf - 2).* = @truncate(u8, z >> 8);
808 },
809 2 => @intToPtr(*u8, buf - 2).* = @truncate(u8, z >> 8),
810 1 => {},
811 else => unreachable,
812 }
813 @intToPtr(*u8, buf - 1).* = @truncate(u8, z);
814 return;
815 }
816 }
817 }
818 }
819
820 fn montymul(
821 comptime Curve: type,
822 out: *[jacobian_len(Curve)]u32,
823 x: [jacobian_len(Curve)]u32,
824 y: [jacobian_len(Curve)]u32,
825 m: [jacobian_len(Curve)]u32,
826 m0i: u32,
827 ) void {
828 comptime const jaclen = jacobian_len(Curve);
829 const len = (m[0] + 31) >> 5;
830 const len4 = len & ~@as(usize, 3);
831 set_zero(jaclen, out, m[0]);
832 var dh: u32 = 0;
833 var u: usize = 0;
834 while (u < len) : (u += 1) {
835 const xu = x[u + 1];
836 const f = MUL31_lo(out[1] + MUL31_lo(x[u + 1], y[1]), m0i);
837
838 var r: u64 = 0;
839 var v: usize = 0;
840 while (v < len4) : (v += 4) {
841 comptime var j = 1;
842 inline while (j <= 4) : (j += 1) {
843 const z = out[v + j] +% MUL31(xu, y[v + j]) +% MUL31(f, m[v + j]) +% r;
844 r = z >> 31;
845 out[v + j - 1] = @truncate(u32, z) & 0x7FFFFFFF;
846 }
847 }
848 while (v < len) : (v += 1) {
849 const z = out[v + 1] +% MUL31(xu, y[v + 1]) +% MUL31(f, m[v + 1]) +% r;
850 r = z >> 31;
851 out[v] = @truncate(u32, z) & 0x7FFFFFFF;
852 }
853 dh += @truncate(u32, r);
854 out[len] = dh & 0x7FFFFFFF;
855 dh >>= 31;
856 }
857 out[0] = m[0];
858 const ctl = NEQ(dh, 0) | NOT(sub(jaclen, out, m, 0));
859 _ = sub(jaclen, out, m, ctl);
860 }
861
862 fn add(comptime len: usize, a: *[len]u32, b: [len]u32, ctl: u32) u32 {
863 var u: usize = 1;
864 var cc: u32 = 0;
865 while (u < len) : (u += 1) {
866 const aw = a[u];
867 const bw = b[u];
868 const naw = aw +% bw +% cc;
869 cc = naw >> 31;
870 a[u] = MUX(ctl, naw & 0x7FFFFFFF, aw);
871 }
872 return cc;
873 }
874
875 fn sub(comptime len: usize, a: *[len]u32, b: [len]u32, ctl: u32) u32 {
876 var cc: u32 = 0;
877 const m = (a[0] + 63) >> 5;
878 var u: usize = 1;
879 while (u < m) : (u += 1) {
880 const aw = a[u];
881 const bw = b[u];
882 const naw = aw -% bw -% cc;
883 cc = naw >> 31;
884 a[u] = MUX(ctl, naw & 0x7FFFFFFF, aw);
885 }
886 return cc;
887 }
888
889 fn iszero(comptime len: usize, arr: [len]u32) u32 {
890 var z: u32 = 0;
891 var u: usize = len - 1;
892 while (u > 0) : (u -= 1) {
893 z |= arr[u];
894 }
895 return ~(z | @bitCast(u32, -@bitCast(i32, z))) >> 31;
896 }
897};
898
899test "elliptic curve functions with secp384r1 curve" {
900 {
901 // Decode to Jacobian then encode again with no operations
902 var P: ecc.Jacobian(ecc.SECP384R1) = undefined;
903 var res: u32 = ecc.decode_to_jacobian(ecc.SECP384R1, &P, ecc.SECP384R1.base_point);
904 var out: [96]u8 = undefined;
905 ecc.encode_from_jacobian(ecc.SECP384R1, &out, P);
906 std.testing.expectEqual(ecc.SECP384R1.base_point, out);
907
908 // Multiply by one, check that the result is still the base point
909 mem.set(u8, &out, 0);
910 ecc.point_mul(ecc.SECP384R1, &P, &[1]u8{1});
911 ecc.encode_from_jacobian(ecc.SECP384R1, &out, P);
912 std.testing.expectEqual(ecc.SECP384R1.base_point, out);
913 }
914
915 {
916 // @TODO Remove this once std.crypto.rand works in .evented mode
917 var rand = blk: {
918 var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
919 try std.os.getrandom(&seed);
920 break :blk &std.rand.DefaultCsprng.init(seed).random;
921 };
922
923 // Derive a shared secret from a Diffie-Hellman key exchange
924 var seed: [48]u8 = undefined;
925 rand.bytes(&seed);
926 const kp1 = ecc.make_key_pair(ecc.SECP384R1, seed);
927 rand.bytes(&seed);
928 const kp2 = ecc.make_key_pair(ecc.SECP384R1, seed);
929
930 const shared1 = try ecc.scalarmult(ecc.SECP384R1, kp1.public_key, &kp2.secret_key);
931 const shared2 = try ecc.scalarmult(ecc.SECP384R1, kp2.public_key, &kp1.secret_key);
932 std.testing.expectEqual(shared1, shared2);
933 }
934
935 // @TODO Add tests with known points.
936}
libs/iguanatls/src/main.zig created+1499
...@@ -0,0 +1,1499 @@
1const std = @import("std");
2const mem = std.mem;
3const Allocator = mem.Allocator;
4const Sha384 = std.crypto.hash.sha2.Sha384;
5const Sha512 = std.crypto.hash.sha2.Sha512;
6const Sha256 = std.crypto.hash.sha2.Sha256;
7const Hmac256 = std.crypto.auth.hmac.sha2.HmacSha256;
8
9pub const asn1 = @import("asn1.zig");
10pub const x509 = @import("x509.zig");
11pub const crypto = @import("crypto.zig");
12
13const ciphers = @import("ciphersuites.zig");
14pub const ciphersuites = ciphers.suites;
15
16comptime {
17 std.testing.refAllDecls(x509);
18 std.testing.refAllDecls(asn1);
19 std.testing.refAllDecls(crypto);
20}
21
22fn handshake_record_length(reader: anytype) !usize {
23 return try record_length(0x16, reader);
24}
25
26pub fn record_length(t: u8, reader: anytype) !usize {
27 try check_record_type(t, reader);
28 var record_header: [4]u8 = undefined;
29 try reader.readNoEof(&record_header);
30 if (!mem.eql(u8, record_header[0..2], "\x03\x03") and !mem.eql(u8, record_header[0..2], "\x03\x01"))
31 return error.ServerInvalidVersion;
32 return mem.readIntSliceBig(u16, record_header[2..4]);
33}
34
35pub const ServerAlert = error{
36 AlertCloseNotify,
37 AlertUnexpectedMessage,
38 AlertBadRecordMAC,
39 AlertDecryptionFailed,
40 AlertRecordOverflow,
41 AlertDecompressionFailure,
42 AlertHandshakeFailure,
43 AlertNoCertificate,
44 AlertBadCertificate,
45 AlertUnsupportedCertificate,
46 AlertCertificateRevoked,
47 AlertCertificateExpired,
48 AlertCertificateUnknown,
49 AlertIllegalParameter,
50 AlertUnknownCA,
51 AlertAccessDenied,
52 AlertDecodeError,
53 AlertDecryptError,
54 AlertExportRestriction,
55 AlertProtocolVersion,
56 AlertInsufficientSecurity,
57 AlertInternalError,
58 AlertUserCanceled,
59 AlertNoRenegotiation,
60 AlertUnsupportedExtension,
61};
62
63fn check_record_type(
64 expected: u8,
65 reader: anytype,
66) (@TypeOf(reader).Error || ServerAlert || error{ ServerMalformedResponse, EndOfStream })!void {
67 const record_type = try reader.readByte();
68 // Alert
69 if (record_type == 0x15) {
70 // Skip SSL version, length of record
71 try reader.skipBytes(4, .{});
72
73 const severity = try reader.readByte();
74 const err_num = try reader.readByte();
75 return switch (err_num) {
76 0 => error.AlertCloseNotify,
77 10 => error.AlertUnexpectedMessage,
78 20 => error.AlertBadRecordMAC,
79 21 => error.AlertDecryptionFailed,
80 22 => error.AlertRecordOverflow,
81 30 => error.AlertDecompressionFailure,
82 40 => error.AlertHandshakeFailure,
83 41 => error.AlertNoCertificate,
84 42 => error.AlertBadCertificate,
85 43 => error.AlertUnsupportedCertificate,
86 44 => error.AlertCertificateRevoked,
87 45 => error.AlertCertificateExpired,
88 46 => error.AlertCertificateUnknown,
89 47 => error.AlertIllegalParameter,
90 48 => error.AlertUnknownCA,
91 49 => error.AlertAccessDenied,
92 50 => error.AlertDecodeError,
93 51 => error.AlertDecryptError,
94 60 => error.AlertExportRestriction,
95 70 => error.AlertProtocolVersion,
96 71 => error.AlertInsufficientSecurity,
97 80 => error.AlertInternalError,
98 90 => error.AlertUserCanceled,
99 100 => error.AlertNoRenegotiation,
100 110 => error.AlertUnsupportedExtension,
101 else => error.ServerMalformedResponse,
102 };
103 }
104 if (record_type != expected)
105 return error.ServerMalformedResponse;
106}
107
108fn Sha256Reader(comptime Reader: anytype) type {
109 const State = struct {
110 sha256: *Sha256,
111 reader: Reader,
112 };
113 const S = struct {
114 pub fn read(state: State, buffer: []u8) Reader.Error!usize {
115 const amt = try state.reader.read(buffer);
116 if (amt != 0) {
117 state.sha256.update(buffer[0..amt]);
118 }
119 return amt;
120 }
121 };
122 return std.io.Reader(State, Reader.Error, S.read);
123}
124
125fn sha256_reader(sha256: *Sha256, reader: anytype) Sha256Reader(@TypeOf(reader)) {
126 return .{ .context = .{ .sha256 = sha256, .reader = reader } };
127}
128
129fn Sha256Writer(comptime Writer: anytype) type {
130 const State = struct {
131 sha256: *Sha256,
132 writer: Writer,
133 };
134 const S = struct {
135 pub fn write(state: State, buffer: []const u8) Writer.Error!usize {
136 const amt = try state.writer.write(buffer);
137 if (amt != 0) {
138 state.sha256.update(buffer[0..amt]);
139 }
140 return amt;
141 }
142 };
143 return std.io.Writer(State, Writer.Error, S.write);
144}
145
146fn sha256_writer(sha256: *Sha256, writer: anytype) Sha256Writer(@TypeOf(writer)) {
147 return .{ .context = .{ .sha256 = sha256, .writer = writer } };
148}
149
150fn CertificateReaderState(comptime Reader: type) type {
151 return struct {
152 reader: Reader,
153 length: usize,
154 idx: usize = 0,
155 };
156}
157
158fn CertificateReader(comptime Reader: type) type {
159 const S = struct {
160 pub fn read(state: *CertificateReaderState(Reader), buffer: []u8) Reader.Error!usize {
161 const out_bytes = std.math.min(buffer.len, state.length - state.idx);
162 const res = try state.reader.readAll(buffer[0..out_bytes]);
163 state.idx += res;
164 return res;
165 }
166 };
167
168 return std.io.Reader(*CertificateReaderState(Reader), Reader.Error, S.read);
169}
170
171pub const CertificateVerifier = union(enum) {
172 none,
173 function: anytype,
174 default,
175};
176
177pub fn CertificateVerifierReader(comptime Reader: type) type {
178 return CertificateReader(Sha256Reader(Reader));
179}
180
181pub fn ClientConnectError(comptime verifier: CertificateVerifier, comptime Reader: type, comptime Writer: type) type {
182 const Additional = error{
183 ServerInvalidVersion,
184 ServerMalformedResponse,
185 EndOfStream,
186 ServerInvalidCipherSuite,
187 ServerInvalidCompressionMethod,
188 ServerInvalidRenegotiationData,
189 ServerInvalidECPointCompression,
190 ServerInvalidProtocol,
191 ServerInvalidExtension,
192 ServerInvalidCurve,
193 ServerInvalidSignature,
194 ServerInvalidSignatureAlgorithm,
195 ServerAuthenticationFailed,
196 ServerInvalidVerifyData,
197 PreMasterGenerationFailed,
198 OutOfMemory,
199 };
200 const err_msg = "Certificate verifier function cannot be generic, use CertificateVerifierReader to get the reader argument type";
201 return Reader.Error || Writer.Error || ServerAlert || Additional || switch (verifier) {
202 .none => error{},
203 .function => |f| @typeInfo(@typeInfo(@TypeOf(f)).Fn.return_type orelse
204 @compileError(err_msg)).ErrorUnion.error_set || error{CertificateVerificationFailed},
205 .default => error{CertificateVerificationFailed},
206 };
207}
208
209// See http://howardhinnant.github.io/date_algorithms.html
210// Timestamp in seconds, only supports A.D. dates
211fn unix_timestamp_from_civil_date(year: u16, month: u8, day: u8) i64 {
212 var y: i64 = year;
213 if (month <= 2) y -= 1;
214 const era = @divTrunc(y, 400);
215 const yoe = y - era * 400; // [0, 399]
216 const doy = @divTrunc((153 * (month + (if (month > 2) @as(i64, -3) else 9)) + 2), 5) + day - 1; // [0, 365]
217 const doe = yoe * 365 + @divTrunc(yoe, 4) - @divTrunc(yoe, 100) + doy; // [0, 146096]
218 return (era * 146097 + doe - 719468) * 86400;
219}
220
221fn read_der_utc_timestamp(reader: anytype) !i64 {
222 var buf: [17]u8 = undefined;
223
224 const tag = try reader.readByte();
225 if (tag != 0x17)
226 return error.CertificateVerificationFailed;
227 const len = try asn1.der.parse_length(reader);
228 if (len > 17)
229 return error.CertificateVerificationFailed;
230
231 try reader.readNoEof(buf[0..len]);
232 const year = std.fmt.parseUnsigned(u16, buf[0..2], 10) catch
233 return error.CertificateVerificationFailed;
234 const month = std.fmt.parseUnsigned(u8, buf[2..4], 10) catch
235 return error.CertificateVerificationFailed;
236 const day = std.fmt.parseUnsigned(u8, buf[4..6], 10) catch
237 return error.CertificateVerificationFailed;
238
239 var time = unix_timestamp_from_civil_date(2000 + year, month, day);
240 time += (std.fmt.parseUnsigned(i64, buf[6..8], 10) catch
241 return error.CertificateVerificationFailed) * 3600;
242 time += (std.fmt.parseUnsigned(i64, buf[8..10], 10) catch
243 return error.CertificateVerificationFailed) * 60;
244
245 if (buf[len - 1] == 'Z') {
246 if (len == 13) {
247 time += std.fmt.parseUnsigned(u8, buf[10..12], 10) catch
248 return error.CertificateVerificationFailed;
249 } else if (len != 11) {
250 return error.CertificateVerificationFailed;
251 }
252 } else {
253 if (len == 15) {
254 if (buf[10] != '+' and buf[10] != '-')
255 return error.CertificateVerificationFailed;
256
257 var additional = (std.fmt.parseUnsigned(i64, buf[11..13], 10) catch
258 return error.CertificateVerificationFailed) * 3600;
259 additional += (std.fmt.parseUnsigned(i64, buf[13..15], 10) catch
260 return error.CertificateVerificationFailed) * 60;
261
262 time += if (buf[10] == '+') -additional else additional;
263 } else if (len == 17) {
264 if (buf[12] != '+' and buf[12] != '-')
265 return error.CertificateVerificationFailed;
266 time += std.fmt.parseUnsigned(u8, buf[10..12], 10) catch
267 return error.CertificateVerificationFailed;
268
269 var additional = (std.fmt.parseUnsigned(i64, buf[13..15], 10) catch
270 return error.CertificateVerificationFailed) * 3600;
271 additional += (std.fmt.parseUnsigned(i64, buf[15..17], 10) catch
272 return error.CertificateVerificationFailed) * 60;
273
274 time += if (buf[12] == '+') -additional else additional;
275 } else return error.CertificateVerificationFailed;
276 }
277 return time;
278}
279
280fn check_cert_timestamp(time: i64, tag_byte: u8, length: usize, reader: anytype) !void {
281 if (time < (try read_der_utc_timestamp(reader)))
282 return error.CertificateVerificationFailed;
283 if (time > (try read_der_utc_timestamp(reader)))
284 return error.CertificateVerificationFailed;
285}
286
287fn add_cert_subject_dn(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void {
288 state.list.items[state.list.items.len - 1].dn = state.fbs.buffer[state.fbs.pos .. state.fbs.pos + length];
289}
290
291fn add_cert_public_key(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void {
292 state.list.items[state.list.items.len - 1].public_key = x509.parse_public_key(
293 state.allocator,
294 reader,
295 ) catch |err| switch (err) {
296 error.MalformedDER => return error.CertificateVerificationFailed,
297 else => |e| return e,
298 };
299}
300
301fn add_server_cert(state: *VerifierCaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
302 const is_ca = state.list.items.len != 0;
303
304 const encoded_length = asn1.der.encode_length(length).slice();
305 const cert_bytes = try state.allocator.alloc(u8, length + 1 + encoded_length.len);
306 errdefer state.allocator.free(cert_bytes);
307 cert_bytes[0] = tag_byte;
308 mem.copy(u8, cert_bytes[1 .. 1 + encoded_length.len], encoded_length);
309
310 try reader.readNoEof(cert_bytes[1 + encoded_length.len ..]);
311 (try state.list.addOne(state.allocator)).* = .{
312 .is_ca = is_ca,
313 .bytes = cert_bytes,
314 .dn = undefined,
315 .public_key = undefined,
316 .signature = asn1.BitString{ .data = &[0]u8{}, .bit_len = 0 },
317 .signature_algorithm = undefined,
318 };
319 errdefer state.allocator.free(state.list.items[state.list.items.len - 1].signature.data);
320
321 const schema = .{
322 .sequence,
323 .{
324 .{ .context_specific, 0 }, // version
325 .{.int}, // serialNumber
326 .{.sequence}, // signature
327 .{.sequence}, // issuer
328 .{ .capture, 0, .sequence }, // validity
329 .{ .capture, 1, .sequence }, // subject
330 .{ .capture, 2, .sequence }, // subjectPublicKeyInfo
331 .{ .optional, .context_specific, 1 }, // issuerUniqueID
332 .{ .optional, .context_specific, 2 }, // subjectUniqueID
333 .{ .optional, .context_specific, 3 }, // extensions
334 },
335 };
336
337 const captures = .{
338 std.time.timestamp(), check_cert_timestamp,
339 state, add_cert_subject_dn,
340 state, add_cert_public_key,
341 };
342
343 var fbs = std.io.fixedBufferStream(@as([]const u8, cert_bytes[1 + encoded_length.len ..]));
344 state.fbs = &fbs;
345
346 asn1.der.parse_schema_tag_len(tag_byte, length, schema, captures, fbs.reader()) catch |err| switch (err) {
347 error.InvalidLength,
348 error.InvalidTag,
349 error.InvalidContainerLength,
350 error.DoesNotMatchSchema,
351 => return error.CertificateVerificationFailed,
352 else => |e| return e,
353 };
354}
355
356fn set_signature_algorithm(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void {
357 const oid_tag = try reader.readByte();
358 if (oid_tag != 0x06)
359 return error.CertificateVerificationFailed;
360
361 const oid_length = try asn1.der.parse_length(reader);
362 if (oid_length == 9) {
363 var oid_bytes: [9]u8 = undefined;
364 try reader.readNoEof(&oid_bytes);
365
366 const cert = &state.list.items[state.list.items.len - 1];
367 if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 })) {
368 cert.signature_algorithm = .rsa;
369 } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 })) {
370 cert.signature_algorithm = .rsa_md5;
371 } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 })) {
372 cert.signature_algorithm = .rsa_sha1;
373 } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B })) {
374 cert.signature_algorithm = .rsa_sha256;
375 } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C })) {
376 cert.signature_algorithm = .rsa_sha384;
377 } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D })) {
378 cert.signature_algorithm = .rsa_sha512;
379 } else {
380 return error.CertificateVerificationFailed;
381 }
382 return;
383 } else if (oid_length == 10) {
384 // @TODO
385 // ECDSA + <Hash> algorithms
386 }
387
388 return error.CertificateVerificationFailed;
389}
390
391fn set_signature_value(state: *VerifierCaptureState, tag: u8, length: usize, reader: anytype) !void {
392 const unused_bits = try reader.readByte();
393 const bit_count = (length - 1) * 8 - unused_bits;
394 const signature_bytes = try state.allocator.alloc(u8, length - 1);
395 errdefer state.allocator.free(signature_bytes);
396 try reader.readNoEof(signature_bytes);
397 state.list.items[state.list.items.len - 1].signature = .{
398 .data = signature_bytes,
399 .bit_len = bit_count,
400 };
401}
402
403fn verify_signature(
404 allocator: *Allocator,
405 signature_algorithm: SignatureAlgorithm,
406 signature: asn1.BitString,
407 hash: []const u8,
408 public_key: x509.PublicKey,
409) !bool {
410 // @TODO ECDSA algorithms
411 if (public_key != .rsa) return false;
412 const prefix: []const u8 = switch (signature_algorithm) {
413 // Deprecated hash algos
414 .rsa_md5, .rsa_sha1 => return false,
415 // @TODO How does this one work?
416 .rsa => return false,
417 .rsa_sha256 => &[_]u8{
418 0x30, 0x31, 0x30, 0x0d, 0x06,
419 0x09, 0x60, 0x86, 0x48, 0x01,
420 0x65, 0x03, 0x04, 0x02, 0x01,
421 0x05, 0x00, 0x04, 0x20,
422 },
423 .rsa_sha384 => &[_]u8{
424 0x30, 0x41, 0x30, 0x0d, 0x06,
425 0x09, 0x60, 0x86, 0x48, 0x01,
426 0x65, 0x03, 0x04, 0x02, 0x02,
427 0x05, 0x00, 0x04, 0x30,
428 },
429 .rsa_sha512 => &[_]u8{
430 0x30, 0x51, 0x30, 0x0d, 0x06,
431 0x09, 0x60, 0x86, 0x48, 0x01,
432 0x65, 0x03, 0x04, 0x02, 0x03,
433 0x05, 0x00, 0x04, 0x40,
434 },
435 };
436
437 // RSA hash verification with PKCS 1 V1_5 padding
438 const modulus = std.math.big.int.Const{ .limbs = public_key.rsa.modulus, .positive = true };
439 const exponent = std.math.big.int.Const{ .limbs = public_key.rsa.exponent, .positive = true };
440 if (modulus.bitCountAbs() != signature.bit_len)
441 return false;
442
443 // encrypt the signature using the RSA key
444 // @TODO better algorithm, this is probably slow as hell
445 var encrypted_signature = try std.math.big.int.Managed.initSet(allocator, @as(usize, 1));
446 defer encrypted_signature.deinit();
447
448 {
449 var curr_exponent = try exponent.toManaged(allocator);
450 defer curr_exponent.deinit();
451
452 const curr_base_limbs = try allocator.alloc(
453 usize,
454 std.math.divCeil(usize, signature.data.len, @sizeOf(usize)) catch unreachable,
455 );
456 const curr_base_limb_bytes = @ptrCast([*]u8, curr_base_limbs)[0..signature.data.len];
457 mem.copy(u8, curr_base_limb_bytes, signature.data);
458 mem.reverse(u8, curr_base_limb_bytes);
459 var curr_base = (std.math.big.int.Mutable{
460 .limbs = curr_base_limbs,
461 .positive = true,
462 .len = curr_base_limbs.len,
463 }).toManaged(allocator);
464 defer curr_base.deinit();
465
466 // encrypted = signature ^ key.exponent MOD key.modulus
467 while (curr_exponent.toConst().orderAgainstScalar(0) == .gt) {
468 if (curr_exponent.isOdd()) {
469 try encrypted_signature.ensureMulCapacity(encrypted_signature.toConst(), curr_base.toConst());
470 try encrypted_signature.mul(encrypted_signature.toConst(), curr_base.toConst());
471 try llmod(&encrypted_signature, modulus);
472 }
473 try curr_base.sqr(curr_base.toConst());
474 try llmod(&curr_base, modulus);
475 try curr_exponent.shiftRight(curr_exponent, 1);
476 }
477 try llmod(&encrypted_signature, modulus);
478 }
479 // EMSA-PKCS1-V1_5-ENCODE
480 if (encrypted_signature.limbs.len * @sizeOf(usize) < signature.data.len)
481 return false;
482
483 const enc_buf = @ptrCast([*]u8, encrypted_signature.limbs.ptr)[0..signature.data.len];
484 mem.reverse(u8, enc_buf);
485
486 if (enc_buf[0] != 0x00 or enc_buf[1] != 0x01)
487 return false;
488 if (!mem.endsWith(u8, enc_buf, hash))
489 return false;
490 if (!mem.endsWith(u8, enc_buf[0 .. enc_buf.len - hash.len], prefix))
491 return false;
492 if (enc_buf[enc_buf.len - hash.len - prefix.len - 1] != 0x00)
493 return false;
494 for (enc_buf[2 .. enc_buf.len - hash.len - prefix.len - 1]) |c| {
495 if (c != 0xff) return false;
496 }
497
498 return true;
499}
500
501fn certificate_verify_signature(
502 allocator: *Allocator,
503 signature_algorithm: SignatureAlgorithm,
504 signature: asn1.BitString,
505 bytes: []const u8,
506 public_key: x509.PublicKey,
507) !bool {
508 // @TODO ECDSA algorithms
509 if (public_key != .rsa) return false;
510
511 var hash_buf: [64]u8 = undefined;
512 var hash: []u8 = undefined;
513
514 switch (signature_algorithm) {
515 // Deprecated hash algos
516 .rsa_md5, .rsa_sha1 => return false,
517 // @TODO How does this one work?
518 .rsa => return false,
519
520 .rsa_sha256 => {
521 Sha256.hash(bytes, hash_buf[0..32], .{});
522 hash = hash_buf[0..32];
523 },
524 .rsa_sha384 => {
525 Sha384.hash(bytes, hash_buf[0..48], .{});
526 hash = hash_buf[0..48];
527 },
528 .rsa_sha512 => {
529 Sha512.hash(bytes, hash_buf[0..64], .{});
530 hash = &hash_buf;
531 },
532 }
533 return try verify_signature(allocator, signature_algorithm, signature, hash, public_key);
534}
535
536// res = res mod N
537fn llmod(res: *std.math.big.int.Managed, n: std.math.big.int.Const) !void {
538 var temp = try std.math.big.int.Managed.init(res.allocator);
539 defer temp.deinit();
540 try temp.divTrunc(res, res.toConst(), n);
541}
542
543const SignatureAlgorithm = enum {
544 rsa,
545 rsa_md5,
546 rsa_sha1,
547 rsa_sha256,
548 rsa_sha384,
549 rsa_sha512,
550 // @TODO ECDSA versions
551};
552
553const ServerCertificate = struct {
554 bytes: []const u8,
555 dn: []const u8,
556 public_key: x509.PublicKey,
557 signature: asn1.BitString,
558 signature_algorithm: SignatureAlgorithm,
559 is_ca: bool,
560};
561
562const VerifierCaptureState = struct {
563 list: std.ArrayListUnmanaged(ServerCertificate),
564 allocator: *Allocator,
565 // Used in `add_server_cert` to avoid an extra allocation
566 fbs: *std.io.FixedBufferStream([]const u8),
567};
568
569pub fn default_cert_verifier(
570 allocator: *std.mem.Allocator,
571 reader: anytype,
572 certs_bytes: usize,
573 trusted_certificates: []const x509.TrustAnchor,
574 hostname: []const u8,
575) !x509.PublicKey {
576 var capture_state = VerifierCaptureState{
577 .list = try std.ArrayListUnmanaged(ServerCertificate).initCapacity(allocator, 3),
578 .allocator = allocator,
579 .fbs = undefined,
580 };
581 defer {
582 for (capture_state.list.items) |cert| {
583 cert.public_key.deinit(allocator);
584 allocator.free(cert.bytes);
585 allocator.free(cert.signature.data);
586 }
587 capture_state.list.deinit(allocator);
588 }
589
590 const schema = .{
591 .sequence, .{
592 // tbsCertificate
593 .{ .capture, 0, .sequence },
594 // signatureAlgorithm
595 .{ .capture, 1, .sequence },
596 // signatureValue
597 .{ .capture, 2, .bit_string },
598 },
599 };
600 const captures = .{
601 &capture_state, add_server_cert,
602 &capture_state, set_signature_algorithm,
603 &capture_state, set_signature_value,
604 };
605
606 var bytes_read: u24 = 0;
607 while (bytes_read < certs_bytes) {
608 const cert_length = try reader.readIntBig(u24);
609
610 asn1.der.parse_schema(schema, captures, reader) catch |err| switch (err) {
611 error.InvalidLength,
612 error.InvalidTag,
613 error.InvalidContainerLength,
614 error.DoesNotMatchSchema,
615 => return error.CertificateVerificationFailed,
616 else => |e| return e,
617 };
618
619 bytes_read += 3 + cert_length;
620 }
621 if (bytes_read != certs_bytes)
622 return error.CertificateVerificationFailed;
623
624 const chain = capture_state.list.items;
625 var i: usize = 0;
626 while (i < chain.len - 1) : (i += 1) {
627 if (!try certificate_verify_signature(
628 allocator,
629 chain[i].signature_algorithm,
630 chain[i].signature,
631 chain[i].bytes,
632 chain[i + 1].public_key,
633 )) {
634 return error.CertificateVerificationFailed;
635 }
636 }
637
638 for (chain) |cert| {
639 for (trusted_certificates) |trusted| {
640 // Try to find an exact match to a trusted certificate
641 if (cert.is_ca == trusted.is_ca and mem.eql(u8, cert.dn, trusted.dn) and
642 cert.public_key.eql(trusted.public_key))
643 {
644 const key = chain[0].public_key;
645 chain[0].public_key = x509.PublicKey{
646 .ec = .{
647 .id = undefined,
648 .curve_point = &[0]u8{},
649 },
650 };
651 return key;
652 }
653
654 if (!trusted.is_ca)
655 continue;
656
657 if (try certificate_verify_signature(
658 allocator,
659 cert.signature_algorithm,
660 cert.signature,
661 cert.bytes,
662 trusted.public_key,
663 )) {
664 const key = chain[0].public_key;
665 chain[0].public_key = x509.PublicKey{
666 .ec = .{
667 .id = undefined,
668 .curve_point = &[0]u8{},
669 },
670 };
671 return key;
672 }
673 }
674 }
675 return error.CertificateVerificationFailed;
676}
677
678pub fn extract_cert_public_key(allocator: *Allocator, reader: anytype, length: usize) !x509.PublicKey {
679 const CaptureState = struct {
680 pub_key: x509.PublicKey,
681 allocator: *Allocator,
682 };
683 var capture_state = CaptureState{
684 .pub_key = undefined,
685 .allocator = allocator,
686 };
687
688 var pub_key: x509.PublicKey = undefined;
689 const schema = .{
690 .sequence, .{
691 // tbsCertificate
692 .{
693 .sequence,
694 .{
695 .{ .context_specific, 0 }, // version
696 .{.int}, // serialNumber
697 .{.sequence}, // signature
698 .{.sequence}, // issuer
699 .{.sequence}, // validity
700 .{.sequence}, // subject
701 .{ .capture, 0, .sequence }, // subjectPublicKeyInfo
702 .{ .optional, .context_specific, 1 }, // issuerUniqueID
703 .{ .optional, .context_specific, 2 }, // subjectUniqueID
704 .{ .optional, .context_specific, 3 }, // extensions
705 },
706 },
707 // signatureAlgorithm
708 .{.sequence},
709 // signatureValue
710 .{.bit_string},
711 },
712 };
713 const captures = .{
714 &capture_state, struct {
715 fn f(state: *CaptureState, tag: u8, _: usize, subreader: anytype) !void {
716 state.pub_key = x509.parse_public_key(state.allocator, subreader) catch |err| switch (err) {
717 error.MalformedDER => return error.ServerMalformedResponse,
718 else => |e| return e,
719 };
720 }
721 }.f,
722 };
723
724 const cert_length = try reader.readIntBig(u24);
725 asn1.der.parse_schema(schema, captures, reader) catch |err| switch (err) {
726 error.InvalidLength,
727 error.InvalidTag,
728 error.InvalidContainerLength,
729 error.DoesNotMatchSchema,
730 => return error.ServerMalformedResponse,
731 else => |e| return e,
732 };
733 errdefer capture_state.pub_key.deinit(allocator);
734
735 try reader.skipBytes(length - cert_length - 3, .{});
736 return capture_state.pub_key;
737}
738
739pub fn client_connect(
740 options: anytype,
741 hostname: []const u8,
742) ClientConnectError(
743 options.cert_verifier,
744 @TypeOf(options.reader),
745 @TypeOf(options.writer),
746)!Client(
747 @TypeOf(options.reader),
748 @TypeOf(options.writer),
749 options.ciphersuites,
750 @hasField(@TypeOf(options), "protocols"),
751) {
752 const Options = @TypeOf(options);
753 if (@TypeOf(options.cert_verifier) != CertificateVerifier and
754 @TypeOf(options.cert_verifier) != @Type(.EnumLiteral))
755 @compileError("cert_verifier should be of type CertificateVerifier");
756
757 if (!@hasField(Options, "temp_allocator"))
758 @compileError("Option tuple is missing field 'temp_allocator'");
759 if (options.cert_verifier == .default) {
760 if (!@hasField(Options, "trusted_certificates"))
761 @compileError("Option tuple is missing field 'trusted_certificates' for .default cert_verifier");
762 }
763
764 const has_alpn = comptime @hasField(Options, "protocols");
765 var handshake_record_hash = Sha256.init(.{});
766 const reader = options.reader;
767 const writer = options.writer;
768 const hashing_reader = sha256_reader(&handshake_record_hash, reader);
769 const hashing_writer = sha256_writer(&handshake_record_hash, writer);
770
771 var client_random: [32]u8 = undefined;
772 const rand = if (!@hasField(Options, "rand"))
773 std.crypto.random
774 else
775 options.rand;
776
777 rand.bytes(&client_random);
778
779 var server_random: [32]u8 = undefined;
780
781 if (options.ciphersuites.len == 0)
782 @compileError("Must provide at least one ciphersuite.");
783 const ciphersuite_bytes = 2 * options.ciphersuites.len + 2;
784 // @TODO Make sure the individual lengths are u16s
785 const alpn_bytes = if (has_alpn) blk: {
786 var sum: usize = 0;
787 for (options.protocols) |proto| {
788 sum += proto.len;
789 }
790 break :blk 6 + options.protocols.len + sum;
791 } else 0;
792 var protocol: if (has_alpn) []const u8 else void = undefined;
793 {
794 const client_hello_start = comptime blk: {
795 // TODO: We assume the compiler is running in a little endian system
796 var starting_part: [46]u8 = [_]u8{
797 // Record header: Handshake record type, protocol version, handshake size
798 0x16, 0x03, 0x01, undefined, undefined,
799 // Handshake message type, bytes of client hello
800 0x01, undefined, undefined, undefined,
801 // Client version (hardcoded to TLS 1.2 even for TLS 1.3)
802 0x03,
803 0x03,
804 } ++ ([1]u8{undefined} ** 32) ++ [_]u8{
805 // Session ID
806 0x00,
807 } ++ mem.toBytes(@byteSwap(u16, ciphersuite_bytes));
808 // using .* = mem.asBytes(...).* or mem.writeIntBig didn't work...
809
810 // Same as above, couldnt achieve this with a single buffer.
811 // TLS_EMPTY_RENEGOTIATION_INFO_SCSV
812 var ciphersuite_buf: []const u8 = &[2]u8{ 0x00, 0x0f };
813 for (options.ciphersuites) |cs, i| {
814 // Also check for properties of the ciphersuites here
815 if (cs.key_exchange != .ecdhe)
816 @compileError("Non ECDHE key exchange is not supported yet.");
817 if (cs.hash != .sha256)
818 @compileError("Non SHA256 hash algorithm is not supported yet.");
819
820 ciphersuite_buf = ciphersuite_buf ++ mem.toBytes(@byteSwap(u16, cs.tag));
821 }
822
823 var ending_part: [13]u8 = [_]u8{
824 // Compression methods (no compression)
825 0x01, 0x00,
826 // Extensions length
827 undefined, undefined,
828 // Extension: server name
829 // id, length, length of entry
830 0x00, 0x00,
831 undefined, undefined,
832 undefined, undefined,
833 // entry type, length of bytes
834 0x00, undefined,
835 undefined,
836 };
837 break :blk starting_part ++ ciphersuite_buf ++ ending_part;
838 };
839
840 var msg_buf = client_hello_start.ptr[0..client_hello_start.len].*;
841 mem.writeIntBig(u16, msg_buf[3..5], @intCast(u16, alpn_bytes + hostname.len + 0x59 + ciphersuite_bytes));
842 mem.writeIntBig(u24, msg_buf[6..9], @intCast(u24, alpn_bytes + hostname.len + 0x55 + ciphersuite_bytes));
843 mem.copy(u8, msg_buf[11..43], &client_random);
844 mem.writeIntBig(u16, msg_buf[48 + ciphersuite_bytes ..][0..2], @intCast(u16, alpn_bytes + hostname.len + 0x2C));
845 mem.writeIntBig(u16, msg_buf[52 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 5));
846 mem.writeIntBig(u16, msg_buf[54 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 3));
847 mem.writeIntBig(u16, msg_buf[57 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len));
848 try writer.writeAll(msg_buf[0..5]);
849 try hashing_writer.writeAll(msg_buf[5..]);
850 }
851 try hashing_writer.writeAll(hostname);
852 // @TODO Fix this with wikipedia test, add secp384r1 support (then app options.curves but default to all when not there (also do this for ciphersuites))
853 if (has_alpn) {
854 var msg_buf = [6]u8{ 0x00, 0x10, undefined, undefined, undefined, undefined };
855 mem.writeIntBig(u16, msg_buf[2..4], @intCast(u16, alpn_bytes - 4));
856 mem.writeIntBig(u16, msg_buf[4..6], @intCast(u16, alpn_bytes - 6));
857 try hashing_writer.writeAll(&msg_buf);
858 for (options.protocols) |proto| {
859 try hashing_writer.writeByte(@intCast(u8, proto.len));
860 try hashing_writer.writeAll(proto);
861 }
862 }
863 try hashing_writer.writeAll(&[35]u8{
864 // Extension: supported groups, for now just x25519 (00 1D) and secp384r1 (00 0x18)
865 0x00, 0x0A, 0x00, 0x06, 0x00, 0x04, 0x00, 0x1D, 0x00, 0x18,
866 // Extension: EC point formats => uncompressed point format
867 0x00, 0x0B, 0x00, 0x02, 0x01, 0x00,
868 // Extension: Signature algorithms
869 // RSA/PKCS1/SHA256, RSA/PKCS1/SHA512
870 0x00, 0x0D, 0x00, 0x06,
871 0x00, 0x04, 0x04, 0x01, 0x06, 0x01,
872 // Extension: Renegotiation Info => new connection
873 0xFF, 0x01, 0x00, 0x01,
874 0x00,
875 // Extension: SCT (signed certificate timestamp)
876 0x00, 0x12, 0x00, 0x00,
877 });
878
879 // Read server hello
880 var ciphersuite: u16 = undefined;
881 {
882 const length = try handshake_record_length(reader);
883 if (length < 44)
884 return error.ServerMalformedResponse;
885 {
886 var hs_hdr_and_server_ver: [6]u8 = undefined;
887 try hashing_reader.readNoEof(&hs_hdr_and_server_ver);
888 if (hs_hdr_and_server_ver[0] != 0x02)
889 return error.ServerMalformedResponse;
890 if (!mem.eql(u8, hs_hdr_and_server_ver[4..6], "\x03\x03"))
891 return error.ServerInvalidVersion;
892 }
893 try hashing_reader.readNoEof(&server_random);
894
895 // Just skip the session id for now
896 const sess_id_len = try hashing_reader.readByte();
897 if (sess_id_len != 0)
898 try hashing_reader.skipBytes(sess_id_len, .{});
899
900 {
901 ciphersuite = try hashing_reader.readIntBig(u16);
902 var found = false;
903 inline for (options.ciphersuites) |cs| {
904 if (ciphersuite == cs.tag) {
905 found = true;
906 // TODO This segfaults stage1
907 // break;
908 }
909 }
910 if (!found)
911 return error.ServerInvalidCipherSuite;
912 }
913
914 // Compression method
915 if ((try hashing_reader.readByte()) != 0x00)
916 return error.ServerInvalidCompressionMethod;
917
918 const exts_length = try hashing_reader.readIntBig(u16);
919 var ext_byte_idx: usize = 0;
920 while (ext_byte_idx < exts_length) {
921 var ext_tag: [2]u8 = undefined;
922 try hashing_reader.readNoEof(&ext_tag);
923
924 const ext_len = try hashing_reader.readIntBig(u16);
925 ext_byte_idx += 4 + ext_len;
926 if (ext_tag[0] == 0xFF and ext_tag[1] == 0x01) {
927 // Renegotiation info
928 const renegotiation_info = try hashing_reader.readByte();
929 if (ext_len != 0x01 or renegotiation_info != 0x00)
930 return error.ServerInvalidRenegotiationData;
931 } else if (ext_tag[0] == 0x00 and ext_tag[1] == 0x00) {
932 // Server name
933 if (ext_len != 0)
934 try hashing_reader.skipBytes(ext_len, .{});
935 } else if (ext_tag[0] == 0x00 and ext_tag[1] == 0x0B) {
936 const format_count = try hashing_reader.readByte();
937 var found_uncompressed = false;
938 var i: usize = 0;
939 while (i < format_count) : (i += 1) {
940 const byte = try hashing_reader.readByte();
941 if (byte == 0x0)
942 found_uncompressed = true;
943 }
944 if (!found_uncompressed)
945 return error.ServerInvalidECPointCompression;
946 } else if (has_alpn and ext_tag[0] == 0x00 and ext_tag[1] == 0x10) {
947 const alpn_ext_len = try hashing_reader.readIntBig(u16);
948 if (alpn_ext_len != ext_len - 2)
949 return error.ServerMalformedResponse;
950 const str_len = try hashing_reader.readByte();
951 var buf: [256]u8 = undefined;
952 try hashing_reader.readNoEof(buf[0..str_len]);
953 const found = for (options.protocols) |proto| {
954 if (mem.eql(u8, proto, buf[0..str_len])) {
955 protocol = proto;
956 break true;
957 }
958 } else false;
959 if (!found)
960 return error.ServerInvalidProtocol;
961 try hashing_reader.skipBytes(alpn_ext_len - str_len - 1, .{});
962 } else return error.ServerInvalidExtension;
963 }
964 if (ext_byte_idx != exts_length)
965 return error.ServerMalformedResponse;
966 }
967 // Read server certificates
968 var certificate_public_key: x509.PublicKey = undefined;
969 {
970 const length = try handshake_record_length(reader);
971 {
972 var handshake_header: [4]u8 = undefined;
973 try hashing_reader.readNoEof(&handshake_header);
974 if (handshake_header[0] != 0x0b)
975 return error.ServerMalformedResponse;
976 }
977 const certs_length = try hashing_reader.readIntBig(u24);
978 const cert_verifier: CertificateVerifier = options.cert_verifier;
979 switch (cert_verifier) {
980 .none => certificate_public_key = try extract_cert_public_key(
981 options.temp_allocator,
982 hashing_reader,
983 certs_length,
984 ),
985 .function => |f| {
986 var reader_state = CertificateReaderState(@TypeOf(hashing_reader)){
987 .reader = hashing_reader,
988 .length = certs_length,
989 };
990 var cert_reader = CertificateReader(@TypeOf(hashing_reader)){ .context = &reader_state };
991 certificate_public_key = try f(cert_reader);
992 try hashing_reader.skipBytes(reader_state.length - reader_state.idx, .{});
993 },
994 .default => certificate_public_key = try default_cert_verifier(
995 options.temp_allocator,
996 hashing_reader,
997 certs_length,
998 options.trusted_certificates,
999 hostname,
1000 ),
1001 }
1002 }
1003 errdefer certificate_public_key.deinit(options.temp_allocator);
1004 // Read server ephemeral public key
1005 var server_public_key_buf: [97]u8 = undefined;
1006 var curve_id: enum { x25519, secp384r1 } = undefined;
1007 {
1008 const length = try handshake_record_length(reader);
1009 {
1010 var handshake_header: [4]u8 = undefined;
1011 try hashing_reader.readNoEof(&handshake_header);
1012 if (handshake_header[0] != 0x0c)
1013 return error.ServerMalformedResponse;
1014
1015 // Only x25519 and secp384r1 supported for now.
1016 var curve_bytes: [3]u8 = undefined;
1017 try hashing_reader.readNoEof(&curve_bytes);
1018 curve_id = if (mem.eql(u8, &curve_bytes, "\x03\x00\x1D"))
1019 .x25519
1020 else if (mem.eql(u8, &curve_bytes, "\x03\x00\x18"))
1021 .secp384r1
1022 else
1023 return error.ServerInvalidCurve;
1024 }
1025
1026 const pub_key_len = try hashing_reader.readByte();
1027 if ((curve_id == .x25519 and pub_key_len != 32) or
1028 (curve_id == .secp384r1 and pub_key_len != 97))
1029 return error.ServerMalformedResponse;
1030 try hashing_reader.readNoEof(server_public_key_buf[0..pub_key_len]);
1031 if (curve_id == .secp384r1 and server_public_key_buf[0] != 0x04)
1032 return error.ServerMalformedResponse;
1033
1034 // Signed public key
1035 const signature_id = try hashing_reader.readIntBig(u16);
1036 const signature_len = try hashing_reader.readIntBig(u16);
1037
1038 var hash_buf: [64]u8 = undefined;
1039 var hash: []const u8 = undefined;
1040 const signature_algoritm: SignatureAlgorithm = switch (signature_id) {
1041 // RSA/PKCS1/SHA256
1042 0x0401 => block: {
1043 var sha256 = Sha256.init(.{});
1044 sha256.update(&client_random);
1045 sha256.update(&server_random);
1046 switch (curve_id) {
1047 .x25519 => sha256.update("\x03\x00\x1D\x20"),
1048 .secp384r1 => sha256.update("\x03\x00\x18\x20"),
1049 }
1050 sha256.update(server_public_key_buf[0..pub_key_len]);
1051 sha256.final(hash_buf[0..32]);
1052 hash = hash_buf[0..32];
1053 break :block .rsa_sha256;
1054 },
1055 // RSA/PKCS1/SHA512
1056 0x0601 => block: {
1057 var sha512 = Sha512.init(.{});
1058 sha512.update(&client_random);
1059 sha512.update(&server_random);
1060 switch (curve_id) {
1061 .x25519 => sha512.update("\x03\x00\x1D"),
1062 .secp384r1 => sha512.update("\x03\x00\x18"),
1063 }
1064 sha512.update(&[1]u8{pub_key_len});
1065 sha512.update(server_public_key_buf[0..pub_key_len]);
1066 sha512.final(hash_buf[0..64]);
1067 hash = hash_buf[0..64];
1068 break :block .rsa_sha512;
1069 },
1070 else => return error.ServerInvalidSignatureAlgorithm,
1071 };
1072 const signature_bytes = try options.temp_allocator.alloc(u8, signature_len);
1073 defer options.temp_allocator.free(signature_bytes);
1074 try hashing_reader.readNoEof(signature_bytes);
1075
1076 if (!try verify_signature(
1077 options.temp_allocator,
1078 signature_algoritm,
1079 .{ .data = signature_bytes, .bit_len = signature_len * 8 },
1080 hash,
1081 certificate_public_key,
1082 ))
1083 return error.ServerInvalidSignature;
1084
1085 certificate_public_key.deinit(options.temp_allocator);
1086 certificate_public_key = x509.PublicKey{ .ec = .{ .id = undefined, .curve_point = &[0]u8{} } };
1087 }
1088 // Read server hello done
1089 {
1090 const length = try handshake_record_length(reader);
1091 const is_bytes = try hashing_reader.isBytes("\x0e\x00\x00\x00");
1092 if (length != 4 or !is_bytes)
1093 return error.ServerMalformedResponse;
1094 }
1095
1096 // Generate keys for the session
1097 const client_key_pair: extern union {
1098 x25519: std.crypto.dh.X25519.KeyPair,
1099 secp384r1: crypto.ecc.KeyPair(crypto.ecc.SECP384R1),
1100 } = switch (curve_id) {
1101 .x25519 => while (true) {
1102 var seed: [32]u8 = undefined;
1103 rand.bytes(&seed);
1104 const tmp = std.crypto.dh.X25519.KeyPair.create(seed) catch continue;
1105 break .{ .x25519 = tmp };
1106 } else unreachable,
1107 .secp384r1 => blk: {
1108 var seed: [48]u8 = undefined;
1109 rand.bytes(&seed);
1110 break :blk .{ .secp384r1 = crypto.ecc.make_key_pair(crypto.ecc.SECP384R1, seed) };
1111 },
1112 };
1113
1114 // Client key exchange
1115 switch (curve_id) {
1116 .x25519 => {
1117 try writer.writeAll(&[5]u8{ 0x16, 0x03, 0x03, 0x00, 0x25 });
1118 try hashing_writer.writeAll(&[5]u8{ 0x10, 0x00, 0x00, 0x21, 0x20 });
1119 try hashing_writer.writeAll(&client_key_pair.x25519.public_key);
1120 },
1121 .secp384r1 => {
1122 try writer.writeAll(&[5]u8{ 0x16, 0x03, 0x03, 0x00, 0x66 });
1123 try hashing_writer.writeAll(&[6]u8{ 0x10, 0x00, 0x00, 0x62, 0x61, 0x04 });
1124 try hashing_writer.writeAll(&client_key_pair.secp384r1.public_key);
1125 },
1126 }
1127 // Client encryption keys calculation for ECDHE_RSA cipher suites with SHA256 hash
1128 var master_secret: [48]u8 = undefined;
1129 var key_data: ciphers.KeyData(options.ciphersuites) = undefined;
1130 {
1131 var pre_master_secret_buf: [96]u8 = undefined;
1132 const pre_master_secret = switch (curve_id) {
1133 .x25519 => blk: {
1134 pre_master_secret_buf[0..32].* = std.crypto.dh.X25519.scalarmult(
1135 client_key_pair.x25519.secret_key,
1136 server_public_key_buf[0..32].*,
1137 ) catch
1138 return error.PreMasterGenerationFailed;
1139 break :blk pre_master_secret_buf[0..32];
1140 },
1141 .secp384r1 => blk: {
1142 pre_master_secret_buf = crypto.ecc.scalarmult(
1143 crypto.ecc.SECP384R1,
1144 server_public_key_buf[1..].*,
1145 &client_key_pair.secp384r1.secret_key,
1146 ) catch
1147 return error.PreMasterGenerationFailed;
1148 break :blk pre_master_secret_buf[0..48];
1149 },
1150 };
1151
1152 var seed: [77]u8 = undefined;
1153 seed[0..13].* = "master secret".*;
1154 seed[13..45].* = client_random;
1155 seed[45..77].* = server_random;
1156
1157 var a1: [32 + seed.len]u8 = undefined;
1158 Hmac256.create(a1[0..32], &seed, pre_master_secret);
1159 var a2: [32 + seed.len]u8 = undefined;
1160 Hmac256.create(a2[0..32], a1[0..32], pre_master_secret);
1161
1162 a1[32..].* = seed;
1163 a2[32..].* = seed;
1164
1165 var p1: [32]u8 = undefined;
1166 Hmac256.create(&p1, &a1, pre_master_secret);
1167 var p2: [32]u8 = undefined;
1168 Hmac256.create(&p2, &a2, pre_master_secret);
1169
1170 master_secret[0..32].* = p1;
1171 master_secret[32..48].* = p2[0..16].*;
1172
1173 // Key expansion
1174 seed[0..13].* = "key expansion".*;
1175 seed[13..45].* = server_random;
1176 seed[45..77].* = client_random;
1177 a1[32..].* = seed;
1178 a2[32..].* = seed;
1179
1180 const KeyExpansionState = struct {
1181 seed: *const [77]u8,
1182 a1: *[32 + seed.len]u8,
1183 a2: *[32 + seed.len]u8,
1184 master_secret: *const [48]u8,
1185 };
1186
1187 const next_32_bytes = struct {
1188 inline fn f(
1189 state: *KeyExpansionState,
1190 comptime chunk_idx: comptime_int,
1191 chunk: *[32]u8,
1192 ) void {
1193 if (chunk_idx == 0) {
1194 Hmac256.create(state.a1[0..32], state.seed, state.master_secret);
1195 Hmac256.create(chunk, state.a1, state.master_secret);
1196 } else if (chunk_idx % 2 == 1) {
1197 Hmac256.create(state.a2[0..32], state.a1[0..32], state.master_secret);
1198 Hmac256.create(chunk, state.a2, state.master_secret);
1199 } else {
1200 Hmac256.create(state.a1[0..32], state.a2[0..32], state.master_secret);
1201 Hmac256.create(chunk, state.a1, state.master_secret);
1202 }
1203 }
1204 }.f;
1205 var state = KeyExpansionState{
1206 .seed = &seed,
1207 .a1 = &a1,
1208 .a2 = &a2,
1209 .master_secret = &master_secret,
1210 };
1211
1212 key_data = ciphers.key_expansion(options.ciphersuites, ciphersuite, &state, next_32_bytes);
1213 }
1214
1215 // Client change cipher spec and client handshake finished
1216 {
1217 try writer.writeAll(&[6]u8{
1218 // Client change cipher spec
1219 0x14, 0x03, 0x03,
1220 0x00, 0x01, 0x01,
1221 });
1222 // The message we need to encrypt is the following:
1223 // 0x14 0x00 0x00 0x0c
1224 // <12 bytes of verify_data>
1225 // seed = "client finished" + SHA256(all handshake messages)
1226 // a1 = HMAC-SHA256(key=MasterSecret, data=seed)
1227 // p1 = HMAC-SHA256(key=MasterSecret, data=a1 + seed)
1228 // verify_data = p1[0..12]
1229 var verify_message: [16]u8 = undefined;
1230 verify_message[0..4].* = "\x14\x00\x00\x0C".*;
1231 {
1232 var seed: [47]u8 = undefined;
1233 seed[0..15].* = "client finished".*;
1234 // We still need to update the hash one time, so we copy
1235 // to get the current digest here.
1236 var hash_copy = handshake_record_hash;
1237 hash_copy.final(seed[15..47]);
1238
1239 var a1: [32 + seed.len]u8 = undefined;
1240 Hmac256.create(a1[0..32], &seed, &master_secret);
1241 a1[32..].* = seed;
1242 var p1: [32]u8 = undefined;
1243 Hmac256.create(&p1, &a1, &master_secret);
1244 verify_message[4..16].* = p1[0..12].*;
1245 }
1246 handshake_record_hash.update(&verify_message);
1247
1248 inline for (options.ciphersuites) |cs| {
1249 if (cs.tag == ciphersuite) {
1250 try cs.raw_write(
1251 256,
1252 rand,
1253 &key_data,
1254 writer,
1255 [3]u8{ 0x16, 0x03, 0x03 },
1256 0,
1257 &verify_message,
1258 );
1259 }
1260 }
1261 }
1262
1263 // Server change cipher spec
1264 {
1265 const length = try record_length(0x14, reader);
1266 const next_byte = try reader.readByte();
1267 if (length != 1 or next_byte != 0x01)
1268 return error.ServerMalformedResponse;
1269 }
1270 // Server handshake finished
1271 {
1272 const length = try handshake_record_length(reader);
1273
1274 var verify_message: [16]u8 = undefined;
1275 verify_message[0..4].* = "\x14\x00\x00\x0C".*;
1276 {
1277 var seed: [47]u8 = undefined;
1278 seed[0..15].* = "server finished".*;
1279 handshake_record_hash.final(seed[15..47]);
1280 var a1: [32 + seed.len]u8 = undefined;
1281 Hmac256.create(a1[0..32], &seed, &master_secret);
1282 a1[32..].* = seed;
1283 var p1: [32]u8 = undefined;
1284 Hmac256.create(&p1, &a1, &master_secret);
1285 verify_message[4..16].* = p1[0..12].*;
1286 }
1287
1288 inline for (options.ciphersuites) |cs| {
1289 if (cs.tag == ciphersuite) {
1290 if (!try cs.check_verify_message(&key_data, length, reader, verify_message))
1291 return error.ServerInvalidVerifyData;
1292 }
1293 }
1294 }
1295
1296 return Client(@TypeOf(reader), @TypeOf(writer), options.ciphersuites, has_alpn){
1297 .ciphersuite = ciphersuite,
1298 .key_data = key_data,
1299 .state = ciphers.client_state_default(options.ciphersuites, ciphersuite),
1300 .rand = rand,
1301 .parent_reader = reader,
1302 .parent_writer = writer,
1303 .protocol = protocol,
1304 };
1305}
1306
1307pub fn Client(
1308 comptime _Reader: type,
1309 comptime _Writer: type,
1310 comptime _ciphersuites: anytype,
1311 comptime has_protocol: bool,
1312) type {
1313 return struct {
1314 const ReaderError = _Reader.Error || ServerAlert || error{ ServerMalformedResponse, ServerInvalidVersion };
1315 pub const Reader = std.io.Reader(*@This(), ReaderError, read);
1316 pub const Writer = std.io.Writer(*@This(), _Writer.Error, write);
1317
1318 ciphersuite: u16,
1319 client_seq: u64 = 1,
1320 server_seq: u64 = 1,
1321 key_data: ciphers.KeyData(_ciphersuites),
1322 state: ciphers.ClientState(_ciphersuites),
1323 rand: *std.rand.Random,
1324
1325 parent_reader: _Reader,
1326 parent_writer: _Writer,
1327
1328 protocol: if (has_protocol) []const u8 else void,
1329
1330 pub fn reader(self: *@This()) Reader {
1331 return .{ .context = self };
1332 }
1333
1334 pub fn writer(self: *@This()) Writer {
1335 return .{ .context = self };
1336 }
1337
1338 pub fn read(self: *@This(), buffer: []u8) ReaderError!usize {
1339 inline for (_ciphersuites) |cs| {
1340 if (self.ciphersuite == cs.tag) {
1341 // @TODO Make this buffer size configurable
1342 return try cs.read(
1343 1024,
1344 &@field(self.state, cs.name),
1345 &self.key_data,
1346 self.parent_reader,
1347 &self.server_seq,
1348 buffer,
1349 );
1350 }
1351 }
1352 unreachable;
1353 }
1354
1355 pub fn write(self: *@This(), buffer: []const u8) _Writer.Error!usize {
1356 if (buffer.len == 0) return 0;
1357
1358 inline for (_ciphersuites) |cs| {
1359 if (self.ciphersuite == cs.tag) {
1360 // @TODO Make this buffer size configurable
1361 const curr_bytes = @truncate(u16, std.math.min(buffer.len, 1024));
1362 try cs.raw_write(
1363 1024,
1364 self.rand,
1365 &self.key_data,
1366 self.parent_writer,
1367 [3]u8{ 0x17, 0x03, 0x03 },
1368 self.client_seq,
1369 buffer[0..curr_bytes],
1370 );
1371 self.client_seq += 1;
1372 return curr_bytes;
1373 }
1374 }
1375 unreachable;
1376 }
1377
1378 pub fn close_notify(self: *@This()) !void {
1379 inline for (_ciphersuites) |cs| {
1380 if (self.ciphersuite == cs.tag) {
1381 try cs.raw_write(
1382 1024,
1383 self.rand,
1384 &self.key_data,
1385 self.parent_writer,
1386 [3]u8{ 0x15, 0x03, 0x03 },
1387 self.client_seq,
1388 "\x01\x00",
1389 );
1390 self.client_seq += 1;
1391 return;
1392 }
1393 }
1394 unreachable;
1395 }
1396 };
1397}
1398
1399test "HTTPS request on wikipedia main page" {
1400 const sock = try std.net.tcpConnectToHost(std.testing.allocator, "en.wikipedia.org", 443);
1401 defer sock.close();
1402
1403 var fbs = std.io.fixedBufferStream(@embedFile("../test/DigiCertHighAssuranceEVRootCA.crt.pem"));
1404 var trusted_chain = try x509.TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader());
1405 defer trusted_chain.deinit();
1406
1407 // @TODO Remove this once std.crypto.rand works in .evented mode
1408 var rand = blk: {
1409 var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
1410 try std.os.getrandom(&seed);
1411 break :blk &std.rand.DefaultCsprng.init(seed).random;
1412 };
1413
1414 var client = try client_connect(.{
1415 .rand = rand,
1416 .reader = sock.reader(),
1417 .writer = sock.writer(),
1418 .cert_verifier = .default,
1419 .temp_allocator = std.testing.allocator,
1420 .trusted_certificates = trusted_chain.data.items,
1421 .ciphersuites = ciphersuites.all,
1422 .protocols = &[_][]const u8{"http/1.1"},
1423 }, "en.wikipedia.org");
1424 defer client.close_notify() catch {};
1425
1426 std.testing.expectEqualStrings("http/1.1", client.protocol);
1427 try client.writer().writeAll("GET /wiki/Main_Page HTTP/1.1\r\nHost: en.wikipedia.org\r\nAccept: */*\r\n\r\n");
1428
1429 {
1430 const header = try client.reader().readUntilDelimiterAlloc(std.testing.allocator, '\n', std.math.maxInt(usize));
1431 std.testing.expectEqualStrings("HTTP/1.1 200 OK", mem.trim(u8, header, &std.ascii.spaces));
1432 std.testing.allocator.free(header);
1433 }
1434
1435 // Skip the rest of the headers expect for Content-Length
1436 var content_length: ?usize = null;
1437 hdr_loop: while (true) {
1438 const header = try client.reader().readUntilDelimiterAlloc(std.testing.allocator, '\n', std.math.maxInt(usize));
1439 defer std.testing.allocator.free(header);
1440
1441 const hdr_contents = mem.trim(u8, header, &std.ascii.spaces);
1442 if (hdr_contents.len == 0) {
1443 break :hdr_loop;
1444 }
1445
1446 if (mem.startsWith(u8, hdr_contents, "Content-Length: ")) {
1447 content_length = try std.fmt.parseUnsigned(usize, hdr_contents[16..], 10);
1448 }
1449 }
1450 std.testing.expect(content_length != null);
1451 const html_contents = try std.testing.allocator.alloc(u8, content_length.?);
1452 defer std.testing.allocator.free(html_contents);
1453
1454 try client.reader().readNoEof(html_contents);
1455}
1456
1457test "HTTPS request on twitch oath2 endpoint" {
1458 const sock = try std.net.tcpConnectToHost(std.testing.allocator, "id.twitch.tv", 443);
1459 defer sock.close();
1460
1461 // @TODO Remove this once std.crypto.rand works in .evented mode
1462 var rand = blk: {
1463 var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
1464 try std.os.getrandom(&seed);
1465 break :blk &std.rand.DefaultCsprng.init(seed).random;
1466 };
1467
1468 var client = try client_connect(.{
1469 .rand = rand,
1470 .temp_allocator = std.testing.allocator,
1471 .reader = sock.reader(),
1472 .writer = sock.writer(),
1473 .cert_verifier = .none,
1474 .ciphersuites = ciphersuites.all,
1475 .protocols = &[_][]const u8{"http/1.1"},
1476 }, "id.twitch.tv");
1477 defer client.close_notify() catch {};
1478
1479 try client.writer().writeAll("GET /oauth2/validate HTTP/1.1\r\nHost: id.twitch.tv\r\nAccept: */*\r\n\r\n");
1480 var content_length: ?usize = null;
1481 hdr_loop: while (true) {
1482 const header = try client.reader().readUntilDelimiterAlloc(std.testing.allocator, '\n', std.math.maxInt(usize));
1483 defer std.testing.allocator.free(header);
1484
1485 const hdr_contents = mem.trim(u8, header, &std.ascii.spaces);
1486 if (hdr_contents.len == 0) {
1487 break :hdr_loop;
1488 }
1489
1490 if (mem.startsWith(u8, hdr_contents, "Content-Length: ")) {
1491 content_length = try std.fmt.parseUnsigned(usize, hdr_contents[16..], 10);
1492 }
1493 }
1494 std.testing.expect(content_length != null);
1495 const html_contents = try std.testing.allocator.alloc(u8, content_length.?);
1496 defer std.testing.allocator.free(html_contents);
1497
1498 try client.reader().readNoEof(html_contents);
1499}
libs/iguanatls/src/x509.zig created+731
...@@ -0,0 +1,731 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const mem = std.mem;
4const trait = std.meta.trait;
5
6const asn1 = @import("asn1.zig");
7
8// zig fmt: off
9// http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8
10// @TODO add backing integer, values
11pub const CurveId = enum {
12 sect163k1, sect163r1, sect163r2, sect193r1,
13 sect193r2, sect233k1, sect233r1, sect239k1,
14 sect283k1, sect283r1, sect409k1, sect409r1,
15 sect571k1, sect571r1, secp160k1, secp160r1,
16 secp160r2, secp192k1, secp192r1, secp224k1,
17 secp224r1, secp256k1, secp256r1, secp384r1,
18 secp521r1,brainpoolP256r1, brainpoolP384r1,
19 brainpoolP512r1, curve25519, curve448,
20};
21// zig fmt: on
22
23pub const PublicKey = union(enum) {
24 /// RSA public key
25 rsa: struct {
26 //Positive std.math.big.int.Const numbers.
27 modulus: []const usize,
28 exponent: []const usize,
29 },
30 /// Elliptic curve public key
31 ec: struct {
32 id: CurveId,
33 /// Public curve point (uncompressed format)
34 curve_point: []const u8,
35 },
36
37 pub fn deinit(self: @This(), alloc: *Allocator) void {
38 switch (self) {
39 .rsa => |rsa| {
40 alloc.free(rsa.modulus);
41 alloc.free(rsa.exponent);
42 },
43 .ec => |ec| alloc.free(ec.curve_point),
44 }
45 }
46
47 pub fn eql(self: @This(), other: @This()) bool {
48 if (@as(@TagType(@This()), self) != @as(@TagType(@This()), other))
49 return false;
50 switch (self) {
51 .rsa => |mod_exp| return mem.eql(usize, mod_exp.exponent, other.rsa.exponent) and
52 mem.eql(usize, mod_exp.modulus, other.rsa.modulus),
53 .ec => |ec| return ec.id == other.ec.id and mem.eql(u8, ec.curve_point, other.ec.curve_point),
54 }
55 }
56};
57
58pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey {
59 if ((try reader.readByte()) != 0x30)
60 return error.MalformedDER;
61 const seq_len = try asn1.der.parse_length(reader);
62
63 if ((try reader.readByte()) != 0x06)
64 return error.MalformedDER;
65 const oid_bytes = try asn1.der.parse_length(reader);
66 if (oid_bytes == 9 ) {
67 // @TODO This fails in async if merged with the if
68 if (!try reader.isBytes(&[9]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1 }))
69 return error.MalformedDER;
70 // OID is 1.2.840.113549.1.1.1
71 // RSA key
72 // Skip past the NULL
73 const null_byte = try reader.readByte();
74 if (null_byte != 0x05)
75 return error.MalformedDER;
76 const null_len = try asn1.der.parse_length(reader);
77 if (null_len != 0x00)
78 return error.MalformedDER;
79 {
80 // BitString next!
81 if ((try reader.readByte()) != 0x03)
82 return error.MalformedDER;
83 _ = try asn1.der.parse_length(reader);
84 const bit_string_unused_bits = try reader.readByte();
85 if (bit_string_unused_bits != 0)
86 return error.MalformedDER;
87
88 if ((try reader.readByte()) != 0x30)
89 return error.MalformedDER;
90 _ = try asn1.der.parse_length(reader);
91
92 // Modulus
93 if ((try reader.readByte()) != 0x02)
94 return error.MalformedDER;
95 const modulus = try asn1.der.parse_int(allocator, reader);
96 errdefer allocator.free(modulus.limbs);
97 if (!modulus.positive) return error.MalformedDER;
98 // Exponent
99 if ((try reader.readByte()) != 0x02)
100 return error.MalformedDER;
101 const exponent = try asn1.der.parse_int(allocator, reader);
102 errdefer allocator.free(exponent.limbs);
103 if (!exponent.positive) return error.MalformedDER;
104 return PublicKey{
105 .rsa = .{
106 .modulus = modulus.limbs,
107 .exponent = exponent.limbs,
108 },
109 };
110 }
111 } else if (oid_bytes == 7) {
112 // @TODO This fails in async if merged with the if
113 if (!try reader.isBytes(&[7]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }))
114 return error.MalformedDER;
115 // OID is 1.2.840.10045.2.1
116 // Elliptical curve
117 // We only support named curves, for which the parameter field is an OID.
118 const oid_tag = try reader.readByte();
119 if (oid_tag != 0x06)
120 return error.MalformedDER;
121 const curve_oid_bytes = try asn1.der.parse_length(reader);
122
123 var key: PublicKey = undefined;
124 if (curve_oid_bytes == 5) {
125 if (!try reader.isBytes(&[4]u8{ 0x2B, 0x81, 0x04, 0x00 }))
126 return error.MalformedDER;
127 // 1.3.132.0.{34, 35}
128 const last_byte = try reader.readByte();
129 if (last_byte == 0x22)
130 key.ec = .{ .id = .secp384r1, .curve_point = undefined }
131 else if (last_byte == 0x23)
132 key.ec = .{ .id = .secp521r1, .curve_point = undefined }
133 else
134 return error.MalformedDER;
135 } else if (curve_oid_bytes == 8)
136 {
137 if (!try reader.isBytes(&[8]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x3, 0x1, 0x7 }))
138 return error.MalformedDER;
139 key.ec = .{ .id = .secp256r1, .curve_point = undefined };
140 } else {
141 return error.MalformedDER;
142 }
143
144 if ((try reader.readByte()) != 0x03)
145 return error.MalformedDER;
146 const byte_len = try asn1.der.parse_length(reader);
147 const unused_bits = try reader.readByte();
148 const bit_count = (byte_len - 1) * 8 - unused_bits;
149 if (bit_count % 8 != 0)
150 return error.MalformedDER;
151 const bit_memory = try allocator.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable);
152 errdefer allocator.free(bit_memory);
153 try reader.readNoEof(bit_memory[0.. byte_len - 1]);
154
155 key.ec.curve_point = bit_memory;
156 return key;
157 }
158 return error.MalformedDER;
159}
160
161pub fn DecodeDERError(comptime Reader: type) type {
162 return Reader.Error || error{
163 MalformedPEM,
164 MalformedDER,
165 EndOfStream,
166 OutOfMemory,
167 };
168}
169
170pub const TrustAnchor = struct {
171 /// Subject distinguished name
172 dn: []const u8,
173 /// A "CA" anchor is deemed fit to verify signatures on certificates.
174 /// A "non-CA" anchor is accepted only for direct trust (server's certificate
175 /// name and key match the anchor).
176 is_ca: bool = false,
177 public_key: PublicKey,
178
179 const CaptureState = struct {
180 self: *TrustAnchor,
181 allocator: *Allocator,
182 dn_allocated: bool = false,
183 pk_allocated: bool = false,
184 };
185 fn initSubjectDn(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
186 const dn_mem = try state.allocator.alloc(u8, length);
187 errdefer state.allocator.free(dn_mem);
188 try reader.readNoEof(dn_mem);
189 state.self.dn = dn_mem;
190 state.dn_allocated = true;
191 }
192
193 fn processExtension(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
194 const object_id = try asn1.der.parse_value(state.allocator, reader);
195 defer object_id.deinit(state.allocator);
196 if (object_id != .object_identifier) return error.DoesNotMatchSchema;
197 if (object_id.object_identifier.len != 4)
198 return;
199
200 const data = object_id.object_identifier.data;
201 // Basic constraints extension
202 if (data[0] != 2 or data[1] != 5 or data[2] != 29 or data[3] != 15)
203 return;
204
205 const basic_constraints = try asn1.der.parse_value(state.allocator, reader);
206 defer basic_constraints.deinit(state.allocator);
207 if (basic_constraints != .bool)
208 return error.DoesNotMatchSchema;
209 state.self.is_ca = basic_constraints.bool;
210 }
211
212 fn initExtensions(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
213 const schema = .{
214 .sequence_of,
215 .{ .capture, 0, .sequence },
216 };
217 const captures = .{
218 state, processExtension,
219 };
220 try asn1.der.parse_schema(schema, captures, reader);
221 }
222
223 fn initPublicKeyInfo(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
224 state.self.public_key = try parse_public_key(state.allocator, reader);
225 state.pk_allocated = true;
226 }
227
228 /// Initialize a trusted anchor from distinguished encoding rules (DER) encoded data
229 pub fn create(allocator: *Allocator, der_reader: anytype) DecodeDERError(@TypeOf(der_reader))!@This() {
230 var self: @This() = undefined;
231 self.is_ca = false;
232 // https://tools.ietf.org/html/rfc5280#page-117
233 const schema = .{
234 .sequence, .{
235 // tbsCertificate
236 .{
237 .sequence,
238 .{
239 .{ .context_specific, 0 }, // version
240 .{.int}, // serialNumber
241 .{.sequence}, // signature
242 .{.sequence}, // issuer
243 .{.sequence}, // validity,
244 .{ .capture, 0, .sequence }, // subject
245 .{ .capture, 1, .sequence }, // subjectPublicKeyInfo
246 .{ .optional, .context_specific, 1 }, // issuerUniqueID
247 .{ .optional, .context_specific, 2 }, // subjectUniqueID
248 .{ .capture, 2, .optional, .context_specific, 3 }, // extensions
249 },
250 },
251 // signatureAlgorithm
252 .{.sequence},
253 // signatureValue
254 .{.bit_string},
255 },
256 };
257
258 var capture_state = CaptureState{
259 .self = &self,
260 .allocator = allocator,
261 };
262 const captures = .{
263 &capture_state, initSubjectDn,
264 &capture_state, initPublicKeyInfo,
265 &capture_state, initExtensions,
266 };
267
268 errdefer {
269 if (capture_state.dn_allocated)
270 allocator.free(self.dn);
271 if (capture_state.pk_allocated)
272 self.public_key.deinit(allocator);
273 }
274
275 asn1.der.parse_schema(schema, captures, der_reader) catch |err| switch (err) {
276 error.InvalidLength,
277 error.InvalidTag,
278 error.InvalidContainerLength,
279 error.DoesNotMatchSchema,
280 => return error.MalformedDER,
281 else => |e| return e,
282 };
283 return self;
284 }
285
286 pub fn deinit(self: @This(), alloc: *Allocator) void {
287 alloc.free(self.dn);
288 self.public_key.deinit(alloc);
289 }
290
291 pub fn format(self: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
292 try writer.print(
293 \\CERTIFICATE
294 \\-----------
295 \\IS CA: {}
296 \\Subject distinguished name (encoded):
297 \\{X}
298 \\Public key:
299 \\
300 , .{ self.is_ca, self.dn });
301
302 switch (self.public_key) {
303 .rsa => |mod_exp| {
304 const modulus = std.math.big.int.Const{ .positive = true, .limbs = mod_exp.modulus };
305 const exponent = std.math.big.int.Const{ .positive = true, .limbs = mod_exp.exponent };
306 try writer.print(
307 \\RSA
308 \\modulus: {}
309 \\exponent: {}
310 \\
311 , .{
312 modulus,
313 exponent,
314 });
315 },
316 .ec => |ec| {
317 try writer.print(
318 \\EC (Curve: {})
319 \\point: {}
320 \\
321 , .{
322 ec.id,
323 ec.curve_point,
324 });
325 },
326 }
327
328 try writer.writeAll(
329 \\-----------
330 \\
331 );
332 }
333};
334
335pub const TrustAnchorChain = struct {
336 data: std.ArrayList(TrustAnchor),
337
338 pub fn from_pem(allocator: *Allocator, pem_reader: anytype) DecodeDERError(@TypeOf(pem_reader))!@This() {
339 var self = @This(){ .data = std.ArrayList(TrustAnchor).init(allocator) };
340 errdefer self.deinit();
341
342 var it = pemCertificateIterator(pem_reader);
343 while (try it.next()) |cert_reader| {
344 var buffered = std.io.bufferedReader(cert_reader);
345 const anchor = try TrustAnchor.create(allocator, buffered.reader());
346 errdefer anchor.deinit(allocator);
347
348 // This read forces the cert reader to find the `-----END`
349 // @TODO Should work without this read, investigate
350 _ = cert_reader.readByte() catch |err| switch (err) {
351 error.EndOfStream => {
352 try self.data.append(anchor);
353 break;
354 },
355 else => |e| return e,
356 };
357 return error.MalformedDER;
358 }
359 return self;
360 }
361
362 pub fn deinit(self: @This()) void {
363 const alloc = self.data.allocator;
364 for (self.data.items) |ta| ta.deinit(alloc);
365 self.data.deinit();
366 }
367};
368
369fn PEMSectionReader(comptime Reader: type) type {
370 const ReadError = Reader.Error || error{MalformedPEM};
371 const S = struct {
372 pub fn read(self: *PEMCertificateIterator(Reader), buffer: []u8) ReadError!usize {
373 const end = "-----END ";
374 var end_letters_matched: ?usize = null;
375
376 var out_idx: usize = 0;
377 if (self.waiting_chars_len > 0) {
378 const rest_written = std.math.min(self.waiting_chars_len, buffer.len);
379 while (out_idx < rest_written) : (out_idx += 1) {
380 buffer[out_idx] = self.waiting_chars[out_idx];
381 }
382
383 self.waiting_chars_len -= rest_written;
384 if (self.waiting_chars_len != 0) {
385 std.mem.copy(u8, self.waiting_chars[0..], self.waiting_chars[rest_written..]);
386 }
387
388 if (out_idx == buffer.len) {
389 return out_idx;
390 }
391 }
392
393 var base64_buf: [4]u8 = undefined;
394 var base64_idx: usize = 0;
395
396 while (true) {
397 var byte = self.reader.readByte() catch |err| switch (err) {
398 error.EndOfStream => {
399 if (self.skip_to_newline_exit) {
400 self.state = .none;
401 return 0;
402 }
403 return error.MalformedPEM;
404 },
405 else => |e| return e,
406 };
407
408 if (self.skip_to_newline_exit) {
409 if (byte == '\n') {
410 self.skip_to_newline_exit = false;
411 self.state = .none;
412 return 0;
413 }
414 continue;
415 }
416
417 if (byte == '\n' or byte == '\r') {
418 self.empty_line = true;
419 continue;
420 }
421 defer self.empty_line = false;
422
423 if (end_letters_matched) |*matched| {
424 if (end[matched.*] == byte) {
425 matched.* += 1;
426 if (matched.* == end.len) {
427 self.skip_to_newline_exit = true;
428 if (out_idx > 0)
429 return out_idx
430 else
431 continue;
432 }
433 continue;
434 } else return error.MalformedPEM;
435 } else if (self.empty_line and end[0] == byte) {
436 end_letters_matched = 1;
437 continue;
438 }
439
440 base64_buf[base64_idx] = byte;
441 base64_idx += 1;
442 if (base64_idx == base64_buf.len) {
443 base64_idx = 0;
444
445 const out_len = std.base64.standard_decoder.calcSize(&base64_buf) catch
446 return error.MalformedPEM;
447 const rest_chars = if (out_len > buffer.len - out_idx)
448 out_len - (buffer.len - out_idx)
449 else
450 0;
451 const buf_chars = out_len - rest_chars;
452
453 var res_buffer: [3]u8 = undefined;
454 std.base64.standard_decoder_unsafe.decode(res_buffer[0..out_len], &base64_buf);
455
456 var i: u3 = 0;
457 while (i < buf_chars) : (i += 1) {
458 buffer[out_idx] = res_buffer[i];
459 out_idx += 1;
460 }
461
462 if (rest_chars > 0) {
463 mem.copy(u8, &self.waiting_chars, res_buffer[i..]);
464 self.waiting_chars_len = @intCast(u2, rest_chars);
465 }
466 if (out_idx == buffer.len)
467 return out_idx;
468 }
469 }
470 }
471 };
472 return std.io.Reader(*PEMCertificateIterator(Reader), ReadError, S.read);
473}
474
475fn PEMCertificateIterator(comptime Reader: type) type {
476 return struct {
477 pub const SectionReader = PEMSectionReader(Reader);
478 pub const NextError = SectionReader.Error || error{EndOfStream};
479
480 reader: Reader,
481 // Internal state for the iterator and the current reader.
482 skip_to_newline_exit: bool = false,
483 empty_line: bool = false,
484 waiting_chars: [4]u8 = undefined,
485 waiting_chars_len: u2 = 0,
486 state: enum {
487 none,
488 in_section_name,
489 in_cert,
490 in_other,
491 } = .none,
492
493 pub fn next(self: *@This()) NextError!?SectionReader {
494 const end = "-----END ";
495 const begin = "-----BEGIN ";
496 const certificate = "CERTIFICATE";
497 const x509_certificate = "X.509 CERTIFICATE";
498
499 var line_empty = true;
500 var end_letters_matched: ?usize = null;
501 var begin_letters_matched: ?usize = null;
502 var certificate_letters_matched: ?usize = null;
503 var x509_certificate_letters_matched: ?usize = null;
504 var skip_to_newline = false;
505 var return_after_skip = false;
506
507 var base64_buf: [4]u8 = undefined;
508 var base64_buf_idx: usize = 0;
509
510 // Called next before reading all of the previous cert.
511 if (self.state == .in_cert) {
512 self.waiting_chars_len = 0;
513 }
514
515 while (true) {
516 var last_byte = false;
517 const byte = self.reader.readByte() catch |err| switch (err) {
518 error.EndOfStream => blk: {
519 if (line_empty and self.state == .none) {
520 return null;
521 } else {
522 last_byte = true;
523 break :blk '\n';
524 }
525 },
526 else => |e| return e,
527 };
528
529 if (skip_to_newline) {
530 if (last_byte)
531 return null;
532 if (byte == '\n') {
533 if (return_after_skip) {
534 return SectionReader{ .context = self };
535 }
536 skip_to_newline = false;
537 line_empty = true;
538 }
539 continue;
540 } else if (byte == '\r' or byte == '\n') {
541 line_empty = true;
542 continue;
543 }
544
545 defer line_empty = byte == '\n' or (line_empty and byte == ' ');
546
547 switch (self.state) {
548 .none => {
549 if (begin_letters_matched) |*matched| {
550 if (begin[matched.*] != byte)
551 return error.MalformedPEM;
552
553 matched.* += 1;
554 if (matched.* == begin.len) {
555 self.state = .in_section_name;
556 line_empty = true;
557 begin_letters_matched = null;
558 }
559 } else if (begin[0] == byte) {
560 begin_letters_matched = 1;
561 } else if (mem.indexOfScalar(u8, &std.ascii.spaces, byte) != null) {
562 if (last_byte) return null;
563 } else return error.MalformedPEM;
564 },
565 .in_section_name => {
566 if (certificate_letters_matched) |*matched| {
567 if (certificate[matched.*] != byte) {
568 self.state = .in_other;
569 skip_to_newline = true;
570 continue;
571 }
572 matched.* += 1;
573 if (matched.* == certificate.len) {
574 self.state = .in_cert;
575 certificate_letters_matched = null;
576 skip_to_newline = true;
577 return_after_skip = true;
578 }
579 } else if (x509_certificate_letters_matched) |*matched| {
580 if (x509_certificate[matched.*] != byte) {
581 self.state = .in_other;
582 skip_to_newline = true;
583 continue;
584 }
585 matched.* += 1;
586 if (matched.* == x509_certificate.len) {
587 self.state = .in_cert;
588 x509_certificate_letters_matched = null;
589 skip_to_newline = true;
590 return_after_skip = true;
591 }
592 } else if (line_empty and certificate[0] == byte) {
593 certificate_letters_matched = 1;
594 } else if (line_empty and x509_certificate[0] == byte) {
595 x509_certificate_letters_matched = 1;
596 } else if (line_empty) {
597 self.state = .in_other;
598 skip_to_newline = true;
599 } else unreachable;
600 },
601 .in_other, .in_cert => {
602 if (end_letters_matched) |*matched| {
603 if (end[matched.*] != byte) {
604 end_letters_matched = null;
605 skip_to_newline = true;
606 continue;
607 }
608 matched.* += 1;
609 if (matched.* == end.len) {
610 self.state = .none;
611 end_letters_matched = null;
612 skip_to_newline = true;
613 }
614 } else if (line_empty and end[0] == byte) {
615 end_letters_matched = 1;
616 }
617 },
618 }
619 }
620 }
621 };
622}
623
624/// Iterator of io.Reader that each decode one certificate from the PEM reader.
625/// Readers do not have to be fully consumed until end of stream, but they must be
626/// read from in order.
627/// Iterator.SectionReader is the type of the io.Reader, Iterator.NextError is the error
628/// set of the next() function.
629pub fn pemCertificateIterator(reader: anytype) PEMCertificateIterator(@TypeOf(reader)) {
630 return .{ .reader = reader };
631}
632
633pub const NameElement = struct {
634 // Encoded OID without tag
635 oid: asn1.ObjectIdentifier,
636 // Destination buffer
637 buf: []u8,
638 status: enum {
639 not_found,
640 found,
641 errored,
642 },
643};
644
645const github_pem = @embedFile("../test/github.pem");
646const github_der = @embedFile("../test/github.der");
647
648fn expected_pem_certificate_chain(bytes: []const u8, certs: []const []const u8) !void {
649 var fbs = std.io.fixedBufferStream(bytes);
650
651 var it = pemCertificateIterator(fbs.reader());
652 var idx: usize = 0;
653 while (try it.next()) |cert_reader| : (idx += 1) {
654 const result_bytes = try cert_reader.readAllAlloc(std.testing.allocator, std.math.maxInt(usize));
655 defer std.testing.allocator.free(result_bytes);
656 std.testing.expectEqualSlices(u8, certs[idx], result_bytes);
657 }
658 if (idx != certs.len) {
659 std.debug.panic("Read {} certificates, wanted {}", .{ idx, certs.len });
660 }
661 std.testing.expect((try it.next()) == null);
662}
663
664fn expected_pem_certificate(bytes: []const u8, cert_bytes: []const u8) !void {
665 try expected_pem_certificate_chain(bytes, &[1][]const u8{cert_bytes});
666}
667
668test "pemCertificateIterator" {
669 try expected_pem_certificate(github_pem, github_der);
670 try expected_pem_certificate(
671 \\-----BEGIN BOGUS-----
672 \\-----END BOGUS-----
673 \\
674 ++
675 github_pem,
676 github_der,
677 );
678
679 try expected_pem_certificate_chain(
680 github_pem ++
681 \\
682 \\-----BEGIN BOGUS-----
683 \\-----END BOGUS-----
684 \\
685 ++ github_pem,
686 &[2][]const u8{ github_der, github_der },
687 );
688
689 try expected_pem_certificate_chain(
690 \\-----BEGIN BOGUS-----
691 \\-----END BOGUS-----
692 \\
693 ,
694 &[0][]const u8{},
695 );
696
697 // Try reading byte by byte from a cert reader
698 {
699 var fbs = std.io.fixedBufferStream(github_pem ++ "\n" ++ github_pem);
700 var it = pemCertificateIterator(fbs.reader());
701
702 // Read a couple of bytes from the first reader, then skip to the next
703 {
704 const first_reader = (try it.next()) orelse return error.NoCertificate;
705 var first_few: [8]u8 = undefined;
706 const bytes = try first_reader.readAll(&first_few);
707 std.testing.expectEqual(first_few.len, bytes);
708 std.testing.expectEqualSlices(u8, github_der[0..bytes], &first_few);
709 }
710
711 const next_reader = (try it.next()) orelse return error.NoCertificate;
712 var idx: usize = 0;
713 while (true) : (idx += 1) {
714 const byte = next_reader.readByte() catch |err| switch (err) {
715 error.EndOfStream => break,
716 else => |e| return e,
717 };
718 if (github_der[idx] != byte) {
719 std.debug.panic("index {}: expected 0x{X}, found 0x{X}", .{ idx, github_der[idx], byte });
720 }
721 }
722 std.testing.expectEqual(github_der.len, idx);
723 std.testing.expect((try it.next()) == null);
724 }
725}
726
727test "TrustAnchorChain" {
728 var fbs = std.io.fixedBufferStream(github_pem);
729 const chain = try TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader());
730 defer chain.deinit();
731}
libs/iguanatls/test/DigiCertHighAssuranceEVRootCA.crt.pem created+23
...@@ -0,0 +1,23 @@
1-----BEGIN CERTIFICATE-----
2MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
3MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
4d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
5ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
6MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
7LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
8RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
9+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
10PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
11xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
12Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
13hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
14EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
15MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
16FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
17nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
18eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
19hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
20Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
21vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
22+OkuE6N36B9K
23-----END CERTIFICATE-----
libs/iguanatls/test/github.der
Binary files /dev/null and b/libs/iguanatls/test/github.der differ
libs/iguanatls/test/github.pem created+23
...@@ -0,0 +1,23 @@
1-----BEGIN CERTIFICATE-----
2MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
3MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
4d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
5ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
6MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
7LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
8RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
9+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
10PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
11xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
12Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
13hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
14EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
15MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
16FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
17nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
18eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
19hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
20Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
21vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
22+OkuE6N36B9K
23-----END CERTIFICATE-----
libs/iguanatls/zig.mod created+4
...@@ -0,0 +1,4 @@
1id: csbnipaad8n77buaszsnjvlmn6j173fl7pkprsctelswjywe
2name: iguanatls
3main: src/main.zig
4dependencies:
libs/zuri/.github/workflows/ci.yml created+22
...@@ -0,0 +1,22 @@
1name: CI
2on: [push]
3jobs:
4 test:
5 strategy:
6 matrix:
7 os: [ubuntu-latest]
8 runs-on: ${{matrix.os}}
9 steps:
10 - uses: actions/checkout@v2
11 - uses: goto-bus-stop/setup-zig@v1.2.1
12 with:
13 version: master
14 - run: zig build test
15 fmt:
16 runs-on: ubuntu-latest
17 steps:
18 - uses: actions/checkout@v2
19 - uses: goto-bus-stop/setup-zig@v1.2.1
20 with:
21 version: master
22 - run: zig fmt build.zig src --check
\ No newline at end of file
libs/zuri/.gitignore created+2
...@@ -0,0 +1,2 @@
1.vscode/
2zig-cache/
\ No newline at end of file
libs/zuri/LICENSE created+21
...@@ -0,0 +1,21 @@
1MIT License
2
3Copyright (c) 2019 Vexu
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in all
13copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21SOFTWARE.
\ No newline at end of file
libs/zuri/README.md created+16
...@@ -0,0 +1,16 @@
1# zuri
2[URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) parser written in [Zig](https://ziglang.org/).
3
4## Example
5```Zig
6const uri = try Uri.parse("https://ziglang.org/documentation/master/#toc-Introduction");
7assert(mem.eql(u8, uri.scheme, "https"));
8assert(mem.eql(u8, uri.host, "ziglang.org"));
9assert(mem.eql(u8, uri.path, "/documentation/master/"));
10assert(mem.eql(u8, uri.fragment, "toc-Introduction"));
11```
12
13## Zig version
14By default the master branch version requires the latest Zig master version.
15
16For Zig 0.6.0 use commit 89ff8258ccd09d0a4cfda649828cc1c45f2b1f8f.
\ No newline at end of file
libs/zuri/build.zig created+10
...@@ -0,0 +1,10 @@
1const Builder = @import("std").build.Builder;
2
3pub fn build(b: *Builder) void {
4 const tests = b.addTest("test.zig");
5 tests.setBuildMode(b.standardReleaseOptions());
6
7 const test_step = b.step("test", "Run library tests");
8 test_step.dependOn(&tests.step);
9 b.default_step.dependOn(test_step);
10}
libs/zuri/src/zuri.zig created+592
...@@ -0,0 +1,592 @@
1const std = @import("std");
2const mem = std.mem;
3const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;
5const parseUnsigned = std.fmt.parseUnsigned;
6const net = std.net;
7const expect = std.testing.expect;
8
9const ValueMap = std.StringHashMap([]const u8);
10
11pub const Uri = struct {
12 scheme: []const u8,
13 username: []const u8,
14 password: []const u8,
15 host: Host,
16 port: ?u16,
17 path: []const u8,
18 query: []const u8,
19 fragment: []const u8,
20 len: usize,
21
22 /// possible uri host values
23 pub const Host = union(enum) {
24 ip: net.Address,
25 name: []const u8,
26 };
27
28 /// possible errors for mapQuery
29 pub const MapError = error{
30 NoQuery,
31 OutOfMemory,
32 };
33
34 /// map query string into a hashmap of key value pairs with no value being an empty string
35 pub fn mapQuery(allocator: *Allocator, query: []const u8) MapError!ValueMap {
36 if (query.len == 0) {
37 return error.NoQuery;
38 }
39 var map = ValueMap.init(allocator);
40 errdefer map.deinit();
41 var start: u32 = 0;
42 var mid: u32 = 0;
43 for (query) |c, i| {
44 if (c == ';' or c == '&') {
45 if (mid != 0) {
46 _ = try map.put(query[start..mid], query[mid + 1 .. i]);
47 } else {
48 _ = try map.put(query[start..i], "");
49 }
50 start = @truncate(u32, i + 1);
51 mid = 0;
52 } else if (c == '=') {
53 mid = @truncate(u32, i);
54 }
55 }
56 if (mid != 0) {
57 _ = try map.put(query[start..mid], query[mid + 1 ..]);
58 } else {
59 _ = try map.put(query[start..], "");
60 }
61
62 return map;
63 }
64
65 /// possible errors for decode and encode
66 pub const EncodeError = error{
67 InvalidCharacter,
68 OutOfMemory,
69 };
70
71 /// decode path if it is percent encoded
72 pub fn decode(allocator: *Allocator, path: []const u8) EncodeError!?[]u8 {
73 var ret: ?[]u8 = null;
74 var ret_index: usize = 0;
75 var i: usize = 0;
76
77 while (i < path.len) : (i += 1) {
78 if (path[i] == '%') {
79 if (!isPchar(path[i..])) {
80 return error.InvalidCharacter;
81 }
82 if (ret == null) {
83 ret = try allocator.alloc(u8, path.len);
84 mem.copy(u8, ret.?, path[0..i]);
85 ret_index = i;
86 }
87
88 // charToDigit can't fail because the chars are validated earlier
89 var new = (std.fmt.charToDigit(path[i + 1], 16) catch unreachable) << 4;
90 new |= std.fmt.charToDigit(path[i + 2], 16) catch unreachable;
91 ret.?[ret_index] = new;
92 ret_index += 1;
93 i += 2;
94 } else if (path[i] != '/' and !isPchar(path[i..])) {
95 return error.InvalidCharacter;
96 } else if (ret != null) {
97 ret.?[ret_index] = path[i];
98 ret_index += 1;
99 }
100 }
101 if (ret != null) {
102 return allocator.realloc(ret.?, ret_index) catch ret.?[0..ret_index];
103 }
104 return ret;
105 }
106
107 /// percent encode if path contains characters not allowed in paths
108 pub fn encode(allocator: *Allocator, path: []const u8) EncodeError!?[]u8 {
109 var ret: ?[]u8 = null;
110 var ret_index: usize = 0;
111 for (path) |c, i| {
112 if (c != '/' and !isPchar(path[i..])) {
113 if (ret == null) {
114 ret = try allocator.alloc(u8, path.len * 3);
115 mem.copy(u8, ret.?, path[0..i]);
116 ret_index = i;
117 }
118 const hex_digits = "0123456789ABCDEF";
119 ret.?[ret_index] = '%';
120 ret.?[ret_index + 1] = hex_digits[(c & 0xF0) >> 4];
121 ret.?[ret_index + 2] = hex_digits[c & 0x0F];
122 ret_index += 3;
123 } else if (ret != null) {
124 ret.?[ret_index] = c;
125 ret_index += 1;
126 }
127 }
128 if (ret != null) {
129 return allocator.realloc(ret.?, ret_index) catch ret.?[0..ret_index];
130 }
131 return ret;
132 }
133
134 /// resolves `path`, leaves trailing '/'
135 /// assumes `path` to be valid
136 pub fn resolvePath(allocator: *Allocator, path: []const u8) error{OutOfMemory}![]u8 {
137 assert(path.len > 0);
138 var list = std.ArrayList([]const u8).init(allocator);
139 errdefer list.deinit();
140
141 var it = mem.tokenize(path, "/");
142 while (it.next()) |p| {
143 if (mem.eql(u8, p, ".")) {
144 continue;
145 } else if (mem.eql(u8, p, "..")) {
146 _ = list.popOrNull();
147 } else {
148 try list.append(p);
149 }
150 }
151
152 var buf = try allocator.alloc(u8, path.len);
153 errdefer allocator.free(buf);
154 var len: usize = 0;
155 var segments = list.toOwnedSlice();
156 defer allocator.free(segments);
157
158 for (segments) |s| {
159 buf[len] = '/';
160 len += 1;
161 mem.copy(u8, buf[len..], s);
162 len += s.len;
163 }
164
165 if (path[path.len - 1] == '/') {
166 buf[len] = '/';
167 len += 1;
168 }
169
170 return allocator.realloc(buf, len) catch buf[0..len];
171 }
172
173 /// possible errors for parse
174 pub const Error = error{
175 /// input is not a valid uri due to a invalid character
176 /// mostly a result of invalid ipv6
177 InvalidCharacter,
178
179 /// given input was empty
180 EmptyUri,
181 };
182
183 /// parse URI from input
184 /// empty input is an error
185 /// if assume_auth is true then `example.com` will result in `example.com` being the host instead of path
186 pub fn parse(input: []const u8, assume_auth: bool) Error!Uri {
187 if (input.len == 0) {
188 return error.EmptyUri;
189 }
190 var uri = Uri{
191 .scheme = "",
192 .username = "",
193 .password = "",
194 .host = .{ .name = "" },
195 .port = null,
196 .path = "",
197 .query = "",
198 .fragment = "",
199 .len = 0,
200 };
201
202 switch (input[0]) {
203 'a'...'z', 'A'...'Z' => {
204 uri.parseMaybeScheme(input);
205 },
206 else => {},
207 }
208
209 if (input.len > uri.len + 2 and input[uri.len] == '/' and input[uri.len + 1] == '/') {
210 uri.len += 2; // for the '//'
211 try uri.parseAuth(input[uri.len..]);
212 } else if (assume_auth) {
213 try uri.parseAuth(input[uri.len..]);
214 }
215
216 // make host ip4 address if possible
217 if (uri.host == .name and uri.host.name.len > 0) blk: {
218 var a = net.Address.parseIp4(uri.host.name, 0) catch break :blk;
219 uri.host = .{ .ip = a }; // workaround for https://github.com/ziglang/zig/issues/3234
220 }
221
222 if (uri.host == .ip and uri.port != null) {
223 uri.host.ip.setPort(uri.port.?);
224 }
225
226 uri.parsePath(input[uri.len..]);
227
228 if (input.len > uri.len + 1 and input[uri.len] == '?') {
229 uri.parseQuery(input[uri.len + 1 ..]);
230 }
231
232 if (input.len > uri.len + 1 and input[uri.len] == '#') {
233 uri.parseFragment(input[uri.len + 1 ..]);
234 }
235 return uri;
236 }
237
238 fn parseMaybeScheme(u: *Uri, input: []const u8) void {
239 for (input) |c, i| {
240 switch (c) {
241 'a'...'z', 'A'...'Z', '0'...'9', '+', '-', '.' => {
242 // allowed characters
243 },
244 ':' => {
245 u.scheme = input[0..i];
246 u.len += u.scheme.len + 1; // +1 for the ':'
247 return;
248 },
249 else => {
250 // not a valid scheme
251 return;
252 },
253 }
254 }
255 return;
256 }
257
258 fn parseAuth(u: *Uri, input: []const u8) Error!void {
259 for (input) |c, i| {
260 switch (c) {
261 '@' => {
262 u.username = input[0..i];
263 u.len += i + 1; // +1 for the '@'
264 return u.parseHost(input[i + 1 ..]);
265 },
266 '[' => {
267 if (i != 0)
268 return error.InvalidCharacter;
269 return u.parseIP(input);
270 },
271 ':' => {
272 u.host.name = input[0..i];
273 u.len += i + 1; // +1 for the '@'
274 return u.parseAuthColon(input[i + 1 ..]);
275 },
276 '/', '?', '#' => {
277 u.host.name = input[0..i];
278 u.len += i;
279 return;
280 },
281 else => if (!isPchar(input)) {
282 u.host.name = input[0..i];
283 u.len += input.len;
284 return;
285 },
286 }
287 }
288 u.host.name = input;
289 u.len += input.len;
290 }
291
292 fn parseAuthColon(u: *Uri, input: []const u8) Error!void {
293 for (input) |c, i| {
294 if (c == '@') {
295 u.username = u.host.name;
296 u.password = input[0..i];
297 u.len += i + 1; //1 for the '@'
298 return u.parseHost(input[i + 1 ..]);
299 } else if (c == '/' or c == '?' or c == '#' or !isPchar(input)) {
300 u.port = parseUnsigned(u16, input[0..i], 10) catch return error.InvalidCharacter;
301 u.len += i;
302 return;
303 }
304 }
305 u.port = parseUnsigned(u16, input, 10) catch return error.InvalidCharacter;
306 u.len += input.len;
307 }
308
309 fn parseHost(u: *Uri, input: []const u8) Error!void {
310 for (input) |c, i| {
311 switch (c) {
312 ':' => {
313 u.host.name = input[0..i];
314 u.len += i + 1; // +1 for the ':'
315 return u.parsePort(input[i..]);
316 },
317 '[' => {
318 if (i != 0)
319 return error.InvalidCharacter;
320 return u.parseIP(input);
321 },
322 else => if (c == '/' or c == '?' or c == '#' or !isPchar(input)) {
323 u.host.name = input[0..i];
324 u.len += i;
325 return;
326 },
327 }
328 }
329 u.host.name = input[0..];
330 u.len += input.len;
331 }
332
333 fn parseIP(u: *Uri, input: []const u8) Error!void {
334 const end = mem.indexOfScalar(u8, input, ']') orelse return error.InvalidCharacter;
335 var addr = net.Address.parseIp6(input[1..end], 0) catch return error.InvalidCharacter;
336 u.host = .{ .ip = addr };
337 u.len += end + 1;
338
339 if (input.len > end + 2 and input[end + 1] == ':') {
340 u.len += 1;
341 try u.parsePort(input[end + 2 ..]);
342 }
343 }
344
345 fn parsePort(u: *Uri, input: []const u8) Error!void {
346 for (input) |c, i| {
347 switch (c) {
348 '0'...'9' => {
349 // digits
350 },
351 else => {
352 if (i == 0) return error.InvalidCharacter;
353 u.port = parseUnsigned(u16, input[0..i], 10) catch return error.InvalidCharacter;
354 u.len += i;
355 return;
356 },
357 }
358 }
359 if (input.len == 0) return error.InvalidCharacter;
360 u.port = parseUnsigned(u16, input[0..], 10) catch return error.InvalidCharacter;
361 u.len += input.len;
362 }
363
364 fn parsePath(u: *Uri, input: []const u8) void {
365 for (input) |c, i| {
366 if (c != '/' and (c == '?' or c == '#' or !isPchar(input[i..]))) {
367 u.path = input[0..i];
368 u.len += u.path.len;
369 return;
370 }
371 }
372 u.path = input[0..];
373 u.len += u.path.len;
374 }
375
376 fn parseQuery(u: *Uri, input: []const u8) void {
377 u.len += 1; // +1 for the '?'
378 for (input) |c, i| {
379 if (c == '#' or (c != '/' and c != '?' and !isPchar(input[i..]))) {
380 u.query = input[0..i];
381 u.len += u.query.len;
382 return;
383 }
384 }
385 u.query = input;
386 u.len += input.len;
387 }
388
389 fn parseFragment(u: *Uri, input: []const u8) void {
390 u.len += 1; // +1 for the '#'
391 for (input) |c, i| {
392 if (c != '/' and c != '?' and !isPchar(input[i..])) {
393 u.fragment = input[0..i];
394 u.len += u.fragment.len;
395 return;
396 }
397 }
398 u.fragment = input;
399 u.len += u.fragment.len;
400 }
401
402 /// returns true if str starts with a valid path character or a percent encoded octet
403 pub fn isPchar(str: []const u8) bool {
404 assert(str.len > 0);
405 return switch (str[0]) {
406 'a'...'z', 'A'...'Z', '0'...'9', '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@' => true,
407 '%' => str.len > 3 and isHex(str[1]) and isHex(str[2]),
408 else => false,
409 };
410 }
411
412 /// returns true if c is a hexadecimal digit
413 pub fn isHex(c: u8) bool {
414 return switch (c) {
415 '0'...'9', 'a'...'f', 'A'...'F' => true,
416 else => false,
417 };
418 }
419};
420
421test "basic url" {
422 const uri = try Uri.parse("https://ziglang.org:80/documentation/master/?test#toc-Introduction", false);
423 expect(mem.eql(u8, uri.scheme, "https"));
424 expect(mem.eql(u8, uri.username, ""));
425 expect(mem.eql(u8, uri.password, ""));
426 expect(mem.eql(u8, uri.host.name, "ziglang.org"));
427 expect(uri.port.? == 80);
428 expect(mem.eql(u8, uri.path, "/documentation/master/"));
429 expect(mem.eql(u8, uri.query, "test"));
430 expect(mem.eql(u8, uri.fragment, "toc-Introduction"));
431 expect(uri.len == 66);
432}
433
434test "short" {
435 const uri = try Uri.parse("telnet://192.0.2.16:80/", false);
436 expect(mem.eql(u8, uri.scheme, "telnet"));
437 expect(mem.eql(u8, uri.username, ""));
438 expect(mem.eql(u8, uri.password, ""));
439 var buf = [_]u8{0} ** 100;
440 var ip = std.fmt.bufPrint(buf[0..], "{}", .{uri.host.ip}) catch unreachable;
441 expect(mem.eql(u8, ip, "192.0.2.16:80"));
442 expect(uri.port.? == 80);
443 expect(mem.eql(u8, uri.path, "/"));
444 expect(mem.eql(u8, uri.query, ""));
445 expect(mem.eql(u8, uri.fragment, ""));
446 expect(uri.len == 23);
447}
448
449test "single char" {
450 const uri = try Uri.parse("a", false);
451 expect(mem.eql(u8, uri.scheme, ""));
452 expect(mem.eql(u8, uri.username, ""));
453 expect(mem.eql(u8, uri.password, ""));
454 expect(mem.eql(u8, uri.host.name, ""));
455 expect(uri.port == null);
456 expect(mem.eql(u8, uri.path, "a"));
457 expect(mem.eql(u8, uri.query, ""));
458 expect(mem.eql(u8, uri.fragment, ""));
459 expect(uri.len == 1);
460}
461
462test "ipv6" {
463 const uri = try Uri.parse("ldap://[2001:db8::7]/c=GB?objectClass?one", false);
464 expect(mem.eql(u8, uri.scheme, "ldap"));
465 expect(mem.eql(u8, uri.username, ""));
466 expect(mem.eql(u8, uri.password, ""));
467 var buf = [_]u8{0} ** 100;
468 var ip = std.fmt.bufPrint(buf[0..], "{}", .{uri.host.ip}) catch unreachable;
469 expect(std.mem.eql(u8, ip, "[2001:db8::7]:0"));
470 expect(uri.port == null);
471 expect(mem.eql(u8, uri.path, "/c=GB"));
472 expect(mem.eql(u8, uri.query, "objectClass?one"));
473 expect(mem.eql(u8, uri.fragment, ""));
474 expect(uri.len == 41);
475}
476
477test "mailto" {
478 const uri = try Uri.parse("mailto:John.Doe@example.com", false);
479 expect(mem.eql(u8, uri.scheme, "mailto"));
480 expect(mem.eql(u8, uri.username, ""));
481 expect(mem.eql(u8, uri.password, ""));
482 expect(mem.eql(u8, uri.host.name, ""));
483 expect(uri.port == null);
484 expect(mem.eql(u8, uri.path, "John.Doe@example.com"));
485 expect(mem.eql(u8, uri.query, ""));
486 expect(mem.eql(u8, uri.fragment, ""));
487 expect(uri.len == 27);
488}
489
490test "tel" {
491 const uri = try Uri.parse("tel:+1-816-555-1212", false);
492 expect(mem.eql(u8, uri.scheme, "tel"));
493 expect(mem.eql(u8, uri.username, ""));
494 expect(mem.eql(u8, uri.password, ""));
495 expect(mem.eql(u8, uri.host.name, ""));
496 expect(uri.port == null);
497 expect(mem.eql(u8, uri.path, "+1-816-555-1212"));
498 expect(mem.eql(u8, uri.query, ""));
499 expect(mem.eql(u8, uri.fragment, ""));
500 expect(uri.len == 19);
501}
502
503test "urn" {
504 const uri = try Uri.parse("urn:oasis:names:specification:docbook:dtd:xml:4.1.2", false);
505 expect(mem.eql(u8, uri.scheme, "urn"));
506 expect(mem.eql(u8, uri.username, ""));
507 expect(mem.eql(u8, uri.password, ""));
508 expect(mem.eql(u8, uri.host.name, ""));
509 expect(uri.port == null);
510 expect(mem.eql(u8, uri.path, "oasis:names:specification:docbook:dtd:xml:4.1.2"));
511 expect(mem.eql(u8, uri.query, ""));
512 expect(mem.eql(u8, uri.fragment, ""));
513 expect(uri.len == 51);
514}
515
516test "userinfo" {
517 const uri = try Uri.parse("ftp://username:password@host.com/", false);
518 expect(mem.eql(u8, uri.scheme, "ftp"));
519 expect(mem.eql(u8, uri.username, "username"));
520 expect(mem.eql(u8, uri.password, "password"));
521 expect(mem.eql(u8, uri.host.name, "host.com"));
522 expect(uri.port == null);
523 expect(mem.eql(u8, uri.path, "/"));
524 expect(mem.eql(u8, uri.query, ""));
525 expect(mem.eql(u8, uri.fragment, ""));
526 expect(uri.len == 33);
527}
528
529test "map query" {
530 const uri = try Uri.parse("https://ziglang.org:80/documentation/master/?test;1=true&false#toc-Introduction", false);
531 expect(mem.eql(u8, uri.scheme, "https"));
532 expect(mem.eql(u8, uri.username, ""));
533 expect(mem.eql(u8, uri.password, ""));
534 expect(mem.eql(u8, uri.host.name, "ziglang.org"));
535 expect(uri.port.? == 80);
536 expect(mem.eql(u8, uri.path, "/documentation/master/"));
537 expect(mem.eql(u8, uri.query, "test;1=true&false"));
538 expect(mem.eql(u8, uri.fragment, "toc-Introduction"));
539 var map = try Uri.mapQuery(alloc, uri.query);
540 defer map.deinit();
541 expect(mem.eql(u8, map.get("test").?, ""));
542 expect(mem.eql(u8, map.get("1").?, "true"));
543 expect(mem.eql(u8, map.get("false").?, ""));
544}
545
546test "ends in space" {
547 const uri = try Uri.parse("https://ziglang.org/documentation/master/ something else", false);
548 expect(mem.eql(u8, uri.scheme, "https"));
549 expect(mem.eql(u8, uri.username, ""));
550 expect(mem.eql(u8, uri.password, ""));
551 expect(mem.eql(u8, uri.host.name, "ziglang.org"));
552 expect(mem.eql(u8, uri.path, "/documentation/master/"));
553 expect(uri.len == 41);
554}
555
556test "assume auth" {
557 const uri = try Uri.parse("ziglang.org", true);
558 expect(mem.eql(u8, uri.host.name, "ziglang.org"));
559 expect(uri.len == 11);
560}
561
562var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
563const alloc = &arena.allocator;
564
565test "encode" {
566 const path = (try Uri.encode(alloc, "/안녕하세요.html")).?;
567 expect(mem.eql(u8, path, "/%EC%95%88%EB%85%95%ED%95%98%EC%84%B8%EC%9A%94.html"));
568}
569
570test "decode" {
571 const path = (try Uri.decode(alloc, "/%EC%95%88%EB%85%95%ED%95%98%EC%84%B8%EC%9A%94.html")).?;
572 expect(mem.eql(u8, path, "/안녕하세요.html"));
573}
574
575test "resolvePath" {
576 var a = try Uri.resolvePath(alloc, "/a/b/..");
577 expect(mem.eql(u8, a, "/a"));
578 a = try Uri.resolvePath(alloc, "/a/b/../");
579 expect(mem.eql(u8, a, "/a/"));
580 a = try Uri.resolvePath(alloc, "/a/b/c/../d/../");
581 expect(mem.eql(u8, a, "/a/b/"));
582 a = try Uri.resolvePath(alloc, "/a/b/c/../d/..");
583 expect(mem.eql(u8, a, "/a/b"));
584 a = try Uri.resolvePath(alloc, "/a/b/c/../d/.././");
585 expect(mem.eql(u8, a, "/a/b/"));
586 a = try Uri.resolvePath(alloc, "/a/b/c/../d/../.");
587 expect(mem.eql(u8, a, "/a/b"));
588 a = try Uri.resolvePath(alloc, "/a/../../");
589 expect(mem.eql(u8, a, "/"));
590
591 arena.deinit();
592}
libs/zuri/test.zig created+3
...@@ -0,0 +1,3 @@
1comptime {
2 _ = @import("src/zuri.zig");
3}