From 0da1bebc31daf70296b708161d6e46bb3567ce9b Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Sat, 20 Feb 2021 19:08:28 -0800 Subject: [PATCH] remove unneeded git submodules --- libs/iguanatls/.gitattributes | 1 - libs/iguanatls/.gitignore | 3 - libs/iguanatls/.gitmodules | 0 libs/iguanatls/LICENSE | 21 - libs/iguanatls/bench/bench.zig | 425 ---- libs/iguanatls/bench/build.zig | 33 - libs/iguanatls/bench/record_handshake.zig | 249 --- libs/iguanatls/build.zig | 14 - libs/iguanatls/gyro.zzz | 14 - libs/iguanatls/src/asn1.zig | 622 ------ libs/iguanatls/src/ciphersuites.zig | 617 ------ libs/iguanatls/src/crypto.zig | 984 --------- libs/iguanatls/src/main.zig | 1843 ----------------- libs/iguanatls/src/x509.zig | 721 ------- .../test/DigiCertGlobalRootCA.crt.pem | 22 - .../DigiCertHighAssuranceEVRootCA.crt.pem | 23 - libs/iguanatls/test/github.der | Bin 969 -> 0 bytes libs/iguanatls/test/github.pem | 23 - libs/iguanatls/zig.mod | 4 - libs/zig-known-folders | 1 - libs/zuri/.github/workflows/ci.yml | 22 - libs/zuri/.gitignore | 2 - libs/zuri/LICENSE | 21 - libs/zuri/README.md | 16 - libs/zuri/build.zig | 10 - libs/zuri/src/zuri.zig | 592 ------ libs/zuri/test.zig | 3 - 27 files changed, 6286 deletions(-) delete mode 100644 libs/iguanatls/.gitattributes delete mode 100644 libs/iguanatls/.gitignore delete mode 100644 libs/iguanatls/.gitmodules delete mode 100644 libs/iguanatls/LICENSE delete mode 100644 libs/iguanatls/bench/bench.zig delete mode 100644 libs/iguanatls/bench/build.zig delete mode 100644 libs/iguanatls/bench/record_handshake.zig delete mode 100644 libs/iguanatls/build.zig delete mode 100644 libs/iguanatls/gyro.zzz delete mode 100644 libs/iguanatls/src/asn1.zig delete mode 100644 libs/iguanatls/src/ciphersuites.zig delete mode 100644 libs/iguanatls/src/crypto.zig delete mode 100644 libs/iguanatls/src/main.zig delete mode 100644 libs/iguanatls/src/x509.zig delete mode 100644 libs/iguanatls/test/DigiCertGlobalRootCA.crt.pem delete mode 100644 libs/iguanatls/test/DigiCertHighAssuranceEVRootCA.crt.pem delete mode 100644 libs/iguanatls/test/github.der delete mode 100644 libs/iguanatls/test/github.pem delete mode 100644 libs/iguanatls/zig.mod delete mode 160000 libs/zig-known-folders delete mode 100644 libs/zuri/.github/workflows/ci.yml delete mode 100644 libs/zuri/.gitignore delete mode 100644 libs/zuri/LICENSE delete mode 100644 libs/zuri/README.md delete mode 100644 libs/zuri/build.zig delete mode 100644 libs/zuri/src/zuri.zig delete mode 100644 libs/zuri/test.zig diff --git a/libs/iguanatls/.gitattributes b/libs/iguanatls/.gitattributes deleted file mode 100644 index 0cb064aeb1e369aa7fffbb2cc732f189bfc77d44..0000000000000000000000000000000000000000 --- a/libs/iguanatls/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.zig text=auto eol=lf diff --git a/libs/iguanatls/.gitignore b/libs/iguanatls/.gitignore deleted file mode 100644 index 63a0a9daad2452036d17e6db33ab58a5fd499e5e..0000000000000000000000000000000000000000 --- a/libs/iguanatls/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/zig-cache -deps.zig -gyro.lock diff --git a/libs/iguanatls/.gitmodules b/libs/iguanatls/.gitmodules deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/libs/iguanatls/LICENSE b/libs/iguanatls/LICENSE deleted file mode 100644 index f830ca8571134f4013938402ed9c531912475a20..0000000000000000000000000000000000000000 --- a/libs/iguanatls/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Alexandros Naskos - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/iguanatls/bench/bench.zig b/libs/iguanatls/bench/bench.zig deleted file mode 100644 index 77dd2ebd945cfcd9754239e3c8122623f9784077..0000000000000000000000000000000000000000 --- a/libs/iguanatls/bench/bench.zig +++ /dev/null @@ -1,425 +0,0 @@ -const std = @import("std"); -const tls = @import("tls"); -const use_gpa = @import("build_options").use_gpa; - -pub const log_level = .debug; - -const RecordingAllocator = struct { - const Stats = struct { - peak_allocated: usize = 0, - total_allocated: usize = 0, - total_deallocated: usize = 0, - total_allocations: usize = 0, - }; - - allocator: std.mem.Allocator = .{ - .allocFn = allocFn, - .resizeFn = resizeFn, - }, - base_allocator: *std.mem.Allocator, - stats: Stats = .{}, - - fn allocFn( - a: *std.mem.Allocator, - len: usize, - ptr_align: u29, - len_align: u29, - ret_addr: usize, - ) ![]u8 { - const self = @fieldParentPtr(RecordingAllocator, "allocator", a); - const mem = try self.base_allocator.allocFn( - self.base_allocator, - len, - ptr_align, - len_align, - ret_addr, - ); - - self.stats.total_allocations += 1; - self.stats.total_allocated += mem.len; - self.stats.peak_allocated = std.math.max( - self.stats.peak_allocated, - self.stats.total_allocated - self.stats.total_deallocated, - ); - return mem; - } - - fn resizeFn(a: *std.mem.Allocator, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) !usize { - const self = @fieldParentPtr(RecordingAllocator, "allocator", a); - const actual_len = try self.base_allocator.resizeFn( - self.base_allocator, - buf, - buf_align, - new_len, - len_align, - ret_addr, - ); - - if (actual_len == 0) { - std.debug.assert(new_len == 0); - self.stats.total_deallocated += buf.len; - } else if (actual_len > buf.len) { - self.stats.total_allocated += actual_len - buf.len; - self.stats.peak_allocated = std.math.max( - self.stats.peak_allocated, - self.stats.total_allocated - self.stats.total_deallocated, - ); - } else { - self.stats.total_deallocated += buf.len - actual_len; - } - return actual_len; - } -}; - -const SinkWriter = blk: { - const S = struct {}; - break :blk std.io.Writer(S, error{}, struct { - fn f(_: S, buffer: []const u8) !usize { - return buffer.len; - } - }.f); -}; - -const ReplayingReaderState = struct { - data: []const u8, -}; -const ReplayingReader = std.io.Reader(*ReplayingReaderState, error{}, struct { - fn f(self: *ReplayingReaderState, buffer: []u8) !usize { - if (self.data.len < buffer.len) - @panic("Not enoguh reader data!"); - std.mem.copy(u8, buffer, self.data[0..buffer.len]); - self.data = self.data[buffer.len..]; - return buffer.len; - } -}.f); - -const ReplayingRandom = struct { - rand: std.rand.Random = .{ .fillFn = fillFn }, - data: []const u8, - - fn fillFn(r: *std.rand.Random, buf: []u8) void { - const self = @fieldParentPtr(ReplayingRandom, "rand", r); - if (self.data.len < buf.len) - @panic("Not enough random data!"); - std.mem.copy(u8, buf, self.data[0..buf.len]); - self.data = self.data[buf.len..]; - } -}; - -fn benchmark_run( - comptime ciphersuites: anytype, - comptime curves: anytype, - gpa: *std.mem.Allocator, - allocator: *std.mem.Allocator, - running_time: f32, - hostname: []const u8, - port: u16, - trust_anchors: tls.x509.TrustAnchorChain, - reader_recording: []const u8, - random_recording: []const u8, -) !void { - { - const warmup_time_secs = std.math.max(0.5, running_time / 20); - std.debug.print("Warming up for {d:.2} seconds...\n", .{warmup_time_secs}); - const warmup_time_ns = @floatToInt(i128, warmup_time_secs * std.time.ns_per_s); - - var warmup_time_passed: i128 = 0; - var timer = try std.time.Timer.start(); - while (warmup_time_passed < warmup_time_ns) { - var rand = ReplayingRandom{ - .data = random_recording, - }; - var reader_state = ReplayingReaderState{ - .data = reader_recording, - }; - const reader = ReplayingReader{ .context = &reader_state }; - const writer = SinkWriter{ .context = .{} }; - - timer.reset(); - _ = try tls.client_connect(.{ - .rand = &rand.rand, - .reader = reader, - .writer = writer, - .ciphersuites = ciphersuites, - .curves = curves, - .cert_verifier = .default, - .temp_allocator = allocator, - .trusted_certificates = trust_anchors.data.items, - }, hostname); - warmup_time_passed += timer.read(); - } - } - { - std.debug.print("Benchmarking for {d:.2} seconds...\n", .{running_time}); - - const RunRecording = struct { - time: i128, - mem_stats: RecordingAllocator.Stats, - }; - var run_recordings = std.ArrayList(RunRecording).init(gpa); - - defer run_recordings.deinit(); - const bench_time_ns = @floatToInt(i128, running_time * std.time.ns_per_s); - - var total_time_passed: i128 = 0; - var iterations: usize = 0; - var timer = try std.time.Timer.start(); - while (total_time_passed < bench_time_ns) : (iterations += 1) { - var rand = ReplayingRandom{ - .data = random_recording, - }; - var reader_state = ReplayingReaderState{ - .data = reader_recording, - }; - const reader = ReplayingReader{ .context = &reader_state }; - const writer = SinkWriter{ .context = .{} }; - var recording_allocator = RecordingAllocator{ .base_allocator = allocator }; - - timer.reset(); - _ = try tls.client_connect(.{ - .rand = &rand.rand, - .reader = reader, - .writer = writer, - .ciphersuites = ciphersuites, - .curves = curves, - .cert_verifier = .default, - .temp_allocator = &recording_allocator.allocator, - .trusted_certificates = trust_anchors.data.items, - }, hostname); - const runtime = timer.read(); - total_time_passed += runtime; - - (try run_recordings.addOne()).* = .{ - .mem_stats = recording_allocator.stats, - .time = runtime, - }; - } - - const total_time_secs = @intToFloat(f64, total_time_passed) / std.time.ns_per_s; - const mean_time_ns = @divTrunc(total_time_passed, iterations); - const mean_time_ms = @intToFloat(f64, mean_time_ns) * std.time.ms_per_s / std.time.ns_per_s; - - const std_dev_ns = blk: { - var acc: i128 = 0; - for (run_recordings.items) |rec| { - const dt = rec.time - mean_time_ns; - acc += dt * dt; - } - break :blk std.math.sqrt(@divTrunc(acc, iterations)); - }; - const std_dev_ms = @intToFloat(f64, std_dev_ns) * std.time.ms_per_s / std.time.ns_per_s; - - std.debug.print( - \\Finished benchmarking. - \\Total runtime: {d:.2} sec - \\Iterations: {} ({d:.2} iterations/sec) - \\Mean iteration time: {d:.2} ms - \\Standard deviation: {d:.2} ms - \\ - , .{ - total_time_secs, - iterations, - @intToFloat(f64, iterations) / total_time_secs, - mean_time_ms, - std_dev_ms, - }); - - // (percentile/100) * (total number n + 1) - std.sort.sort(RunRecording, run_recordings.items, {}, struct { - fn f(_: void, lhs: RunRecording, rhs: RunRecording) bool { - return lhs.time < rhs.time; - } - }.f); - const percentiles = .{ 99.0, 90.0, 75.0, 50.0 }; - inline for (percentiles) |percentile| { - if (percentile < iterations) { - const idx = @floatToInt(usize, @intToFloat(f64, iterations + 1) * percentile / 100.0); - std.debug.print( - "{d:.0}th percentile value: {d:.2} ms\n", - .{ - percentile, - @intToFloat(f64, run_recordings.items[idx].time) * std.time.ms_per_s / std.time.ns_per_s, - }, - ); - } - } - - const first_mem_stats = run_recordings.items[0].mem_stats; - for (run_recordings.items[1..]) |rec| { - std.debug.assert(std.meta.eql(first_mem_stats, rec.mem_stats)); - } - - std.debug.print( - \\Peak allocated memory: {Bi:.2}, - \\Total allocated memory: {Bi:.2}, - \\Number of allocations: {d}, - \\ - , .{ - first_mem_stats.peak_allocated, - first_mem_stats.total_allocated, - first_mem_stats.total_allocations, - }); - } -} - -fn benchmark_run_with_ciphersuite( - comptime ciphersuites: anytype, - curve_str: []const u8, - gpa: *std.mem.Allocator, - allocator: *std.mem.Allocator, - running_time: f32, - hostname: []const u8, - port: u16, - trust_anchors: tls.x509.TrustAnchorChain, - reader_recording: []const u8, - random_recording: []const u8, -) !void { - if (std.mem.eql(u8, curve_str, "all")) { - return try benchmark_run( - ciphersuites, - tls.curves.all, - gpa, - allocator, - running_time, - hostname, - port, - trust_anchors, - reader_recording, - random_recording, - ); - } - inline for (tls.curves.all) |curve| { - if (std.mem.eql(u8, curve_str, curve.name)) { - return try benchmark_run( - ciphersuites, - .{curve}, - gpa, - allocator, - running_time, - hostname, - port, - trust_anchors, - reader_recording, - random_recording, - ); - } - } - return error.InvalidCurve; -} - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = &gpa.allocator; - - var args = std.process.args(); - std.debug.assert(args.skip()); - - const running_time = blk: { - const maybe_arg = args.next(allocator) orelse return error.NoArguments; - const arg = try maybe_arg; - break :blk std.fmt.parseFloat(f32, arg) catch { - std.log.crit("Running time is not a floating point number...", .{}); - return error.InvalidArg; - }; - }; - - // Loop over all files, swap gpa with a fixed buffer allocator for the handhsake - arg_loop: while (args.next(allocator)) |recorded_file_path_or_err| { - const recorded_file_path = try recorded_file_path_or_err; - defer allocator.free(recorded_file_path); - - std.debug.print( - \\============================================================ - \\{s} - \\============================================================ - \\ - , .{std.fs.path.basename(recorded_file_path)}); - - const recorded_file = try std.fs.cwd().openFile(recorded_file_path, .{}); - defer recorded_file.close(); - - const ciphersuite_str_len = try recorded_file.reader().readByte(); - const ciphersuite_str = try allocator.alloc(u8, ciphersuite_str_len); - defer allocator.free(ciphersuite_str); - try recorded_file.reader().readNoEof(ciphersuite_str); - - const curve_str_len = try recorded_file.reader().readByte(); - const curve_str = try allocator.alloc(u8, curve_str_len); - defer allocator.free(curve_str); - try recorded_file.reader().readNoEof(curve_str); - - const hostname_len = try recorded_file.reader().readIntLittle(usize); - const hostname = try allocator.alloc(u8, hostname_len); - defer allocator.free(hostname); - try recorded_file.reader().readNoEof(hostname); - - const port = try recorded_file.reader().readIntLittle(u16); - - const trust_anchors = blk: { - const pem_file_path_len = try recorded_file.reader().readIntLittle(usize); - const pem_file_path = try allocator.alloc(u8, pem_file_path_len); - defer allocator.free(pem_file_path); - try recorded_file.reader().readNoEof(pem_file_path); - - const pem_file = try std.fs.cwd().openFile(pem_file_path, .{}); - defer pem_file.close(); - - const tas = try tls.x509.TrustAnchorChain.from_pem(allocator, pem_file.reader()); - std.debug.print("Read {} certificates.\n", .{tas.data.items.len}); - break :blk tas; - }; - defer trust_anchors.deinit(); - - const reader_recording_len = try recorded_file.reader().readIntLittle(usize); - const reader_recording = try allocator.alloc(u8, reader_recording_len); - defer allocator.free(reader_recording); - try recorded_file.reader().readNoEof(reader_recording); - - const random_recording_len = try recorded_file.reader().readIntLittle(usize); - const random_recording = try allocator.alloc(u8, random_recording_len); - defer allocator.free(random_recording); - try recorded_file.reader().readNoEof(random_recording); - - const handshake_allocator = if (use_gpa) - &gpa.allocator - else - &std.heap.ArenaAllocator.init(std.heap.page_allocator).allocator; - - defer if (!use_gpa) - @fieldParentPtr(std.heap.ArenaAllocator, "allocator", handshake_allocator).deinit(); - - if (std.mem.eql(u8, ciphersuite_str, "all")) { - try benchmark_run_with_ciphersuite( - tls.ciphersuites.all, - curve_str, - allocator, - handshake_allocator, - running_time, - hostname, - port, - trust_anchors, - reader_recording, - random_recording, - ); - continue :arg_loop; - } - inline for (tls.ciphersuites.all) |ciphersuite| { - if (std.mem.eql(u8, ciphersuite_str, ciphersuite.name)) { - try benchmark_run_with_ciphersuite( - .{ciphersuite}, - curve_str, - allocator, - handshake_allocator, - running_time, - hostname, - port, - trust_anchors, - reader_recording, - random_recording, - ); - continue :arg_loop; - } - } - return error.InvalidCiphersuite; - } -} diff --git a/libs/iguanatls/bench/build.zig b/libs/iguanatls/bench/build.zig deleted file mode 100644 index ab36f63ff0ea3af0947cb30c3303656cf3a734ba..0000000000000000000000000000000000000000 --- a/libs/iguanatls/bench/build.zig +++ /dev/null @@ -1,33 +0,0 @@ -const Builder = @import("std").build.Builder; - -pub fn build(b: *Builder) void { - const record_build = b.addExecutable("record_handshake", "record_handshake.zig"); - record_build.addPackagePath("tls", "../src/main.zig"); - record_build.setBuildMode(.Debug); - record_build.install(); - - const use_gpa = b.option( - bool, - "use-gpa", - "Use the general purpose allocator instead of an arena allocator", - ) orelse false; - const bench_build = b.addExecutable("bench", "bench.zig"); - bench_build.addPackagePath("tls", "../src/main.zig"); - bench_build.setBuildMode(.ReleaseFast); - bench_build.addBuildOption(bool, "use_gpa", use_gpa); - bench_build.install(); - - const record_run_cmd = record_build.run(); - const bench_run_cmd = bench_build.run(); - record_run_cmd.step.dependOn(b.getInstallStep()); - bench_run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - record_run_cmd.addArgs(args); - bench_run_cmd.addArgs(args); - } - - const record_run_step = b.step("record-handshake", "Record a TLS handshake"); - const bench_run_step = b.step("bench", "Run the benchmark"); - record_run_step.dependOn(&record_run_cmd.step); - bench_run_step.dependOn(&bench_run_cmd.step); -} diff --git a/libs/iguanatls/bench/record_handshake.zig b/libs/iguanatls/bench/record_handshake.zig deleted file mode 100644 index 4d275fa5b8f55c9fdcb41d4ff0a04c21669fe382..0000000000000000000000000000000000000000 --- a/libs/iguanatls/bench/record_handshake.zig +++ /dev/null @@ -1,249 +0,0 @@ -const std = @import("std"); -const tls = @import("tls"); - -const RecordingRandom = struct { - rand: std.rand.Random = .{ - .fillFn = fillFn, - }, - base: *std.rand.Random, - recorded: std.ArrayList(u8), - - fn fillFn(r: *std.rand.Random, buf: []u8) void { - const self = @fieldParentPtr(@This(), "rand", r); - self.base.bytes(buf); - self.recorded.writer().writeAll(buf) catch unreachable; - } -}; - -fn RecordingReaderState(comptime Base: type) type { - return struct { - base: Base, - recorded: std.ArrayList(u8), - - fn read(self: *@This(), buffer: []u8) !usize { - var read_bytes = try self.base.read(buffer); - if (read_bytes != 0) { - try self.recorded.writer().writeAll(buffer[0..read_bytes]); - } - return read_bytes; - } - }; -} - -fn RecordingReader(comptime Base: type) type { - return std.io.Reader( - *RecordingReaderState(Base), - Base.Error || error{OutOfMemory}, - RecordingReaderState(Base).read, - ); -} - -fn record_handshake( - comptime ciphersuites: anytype, - comptime curves: anytype, - allocator: *std.mem.Allocator, - out_name: []const u8, - hostname: []const u8, - port: u16, - pem_file_path: []const u8, -) !void { - // Read PEM file - const pem_file = try std.fs.cwd().openFile(pem_file_path, .{}); - defer pem_file.close(); - - const trust_anchors = try tls.x509.TrustAnchorChain.from_pem(allocator, pem_file.reader()); - defer trust_anchors.deinit(); - std.log.info("Read {} certificates.", .{trust_anchors.data.items.len}); - - const sock = try std.net.tcpConnectToHost(allocator, hostname, port); - defer sock.close(); - - var recording_reader_state = RecordingReaderState(@TypeOf(sock).Reader){ - .base = sock.reader(), - .recorded = std.ArrayList(u8).init(allocator), - }; - defer recording_reader_state.recorded.deinit(); - - var recording_random = RecordingRandom{ - .base = std.crypto.random, - .recorded = std.ArrayList(u8).init(allocator), - }; - defer recording_random.recorded.deinit(); - - const reader = RecordingReader(@TypeOf(sock).Reader){ - .context = &recording_reader_state, - }; - std.log.info("Recording session `{s}`...", .{out_name}); - var client = try tls.client_connect(.{ - .rand = &recording_random.rand, - .reader = reader, - .writer = sock.writer(), - .ciphersuites = ciphersuites, - .curves = curves, - .cert_verifier = .default, - .temp_allocator = allocator, - .trusted_certificates = trust_anchors.data.items, - }, hostname); - defer client.close_notify() catch {}; - - const out_file = try std.fs.cwd().createFile(out_name, .{}); - defer out_file.close(); - - if (ciphersuites.len > 1) { - try out_file.writeAll(&[_]u8{ 0x3, 'a', 'l', 'l' }); - } else { - try out_file.writer().writeIntLittle(u8, ciphersuites[0].name.len); - try out_file.writeAll(ciphersuites[0].name); - } - if (curves.len > 1) { - try out_file.writeAll(&[_]u8{ 0x3, 'a', 'l', 'l' }); - } else { - try out_file.writer().writeIntLittle(u8, curves[0].name.len); - try out_file.writeAll(curves[0].name); - } - try out_file.writer().writeIntLittle(usize, hostname.len); - try out_file.writeAll(hostname); - try out_file.writer().writeIntLittle(u16, port); - try out_file.writer().writeIntLittle(usize, pem_file_path.len); - try out_file.writeAll(pem_file_path); - try out_file.writer().writeIntLittle(usize, recording_reader_state.recorded.items.len); - try out_file.writeAll(recording_reader_state.recorded.items); - try out_file.writer().writeIntLittle(usize, recording_random.recorded.items.len); - try out_file.writeAll(recording_random.recorded.items); - std.log.info("Session recorded.\n", .{}); -} - -fn record_with_ciphersuite( - comptime ciphersuites: anytype, - allocator: *std.mem.Allocator, - out_name: []const u8, - curve_str: []const u8, - hostname: []const u8, - port: u16, - pem_file_path: []const u8, -) !void { - if (std.mem.eql(u8, curve_str, "all")) { - return try record_handshake( - ciphersuites, - tls.curves.all, - allocator, - out_name, - hostname, - port, - pem_file_path, - ); - } - inline for (tls.curves.all) |curve| { - if (std.mem.eql(u8, curve_str, curve.name)) { - return try record_handshake( - ciphersuites, - .{curve}, - allocator, - out_name, - hostname, - port, - pem_file_path, - ); - } - } - std.log.crit("Invalid curve `{s}`", .{curve_str}); - std.debug.warn("Available options:\n- all\n", .{}); - inline for (tls.curves.all) |curve| { - std.debug.warn("- {s}\n", .{curve.name}); - } - return error.InvalidArg; -} - -var gpa = std.heap.GeneralPurposeAllocator(.{}){}; -pub fn main() !void { - const allocator = &gpa.allocator; - - var args = std.process.args(); - std.debug.assert(args.skip()); - - const pem_file_path = try (args.next(allocator) orelse { - std.log.crit("Need PEM file path as first argument", .{}); - return error.NotEnoughArgs; - }); - defer allocator.free(pem_file_path); - - const ciphersuite_str = try (args.next(allocator) orelse { - std.log.crit("Need ciphersuite as second argument", .{}); - return error.NotEnoughArgs; - }); - defer allocator.free(ciphersuite_str); - - const curve_str = try (args.next(allocator) orelse { - std.log.crit("Need curve as third argument", .{}); - return error.NotEnoughArgs; - }); - defer allocator.free(curve_str); - - const hostname_port = try (args.next(allocator) orelse { - std.log.crit("Need hostname:port as fourth argument", .{}); - return error.NotEnoughArgs; - }); - defer allocator.free(hostname_port); - - if (args.skip()) { - std.log.crit("Need exactly four arguments", .{}); - return error.TooManyArgs; - } - - var hostname_parts = std.mem.split(hostname_port, ":"); - const hostname = hostname_parts.next().?; - const port = std.fmt.parseUnsigned( - u16, - hostname_parts.next() orelse { - std.log.crit("Hostname and port should be in `hostname:port` format", .{}); - return error.InvalidArg; - }, - 10, - ) catch { - std.log.crit("Port is not a base 10 unsigned integer...", .{}); - return error.InvalidArg; - }; - if (hostname_parts.next() != null) { - std.log.crit("Hostname and port should be in `hostname:port` format", .{}); - return error.InvalidArg; - } - - const out_name = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}-{}.handshake", .{ - hostname, - ciphersuite_str, - curve_str, - std.time.timestamp(), - }); - defer allocator.free(out_name); - - if (std.mem.eql(u8, ciphersuite_str, "all")) { - return try record_with_ciphersuite( - tls.ciphersuites.all, - allocator, - out_name, - curve_str, - hostname, - port, - pem_file_path, - ); - } - inline for (tls.ciphersuites.all) |ciphersuite| { - if (std.mem.eql(u8, ciphersuite_str, ciphersuite.name)) { - return try record_with_ciphersuite( - .{ciphersuite}, - allocator, - out_name, - curve_str, - hostname, - port, - pem_file_path, - ); - } - } - std.log.crit("Invalid ciphersuite `{s}`", .{ciphersuite_str}); - std.debug.warn("Available options:\n- all\n", .{}); - inline for (tls.ciphersuites.all) |ciphersuite| { - std.debug.warn("- {s}\n", .{ciphersuite.name}); - } - return error.InvalidArg; -} diff --git a/libs/iguanatls/build.zig b/libs/iguanatls/build.zig deleted file mode 100644 index 9aed4bf0d7a5156ac5bfc7c057493175ae7c54c1..0000000000000000000000000000000000000000 --- a/libs/iguanatls/build.zig +++ /dev/null @@ -1,14 +0,0 @@ -const Builder = @import("std").build.Builder; - -pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - const lib = b.addStaticLibrary("iguanaTLS", "src/main.zig"); - lib.setBuildMode(mode); - lib.install(); - - var main_tests = b.addTest("src/main.zig"); - main_tests.setBuildMode(mode); - - const test_step = b.step("test", "Run library tests"); - test_step.dependOn(&main_tests.step); -} diff --git a/libs/iguanatls/gyro.zzz b/libs/iguanatls/gyro.zzz deleted file mode 100644 index a6971f8b33b5260baa6ec45b42031559ba437134..0000000000000000000000000000000000000000 --- a/libs/iguanatls/gyro.zzz +++ /dev/null @@ -1,14 +0,0 @@ -pkgs: - iguanaTLS: - version: 0.0.0 - author: alexnask - description: "Minimal, experimental TLS 1.2 implementation in Zig" - license: MIT - source_url: "https://github.com/alexnask/iguanaTLS" - - files: - build.zig - README.md - LICENSE - src/*.zig - test/* diff --git a/libs/iguanatls/src/asn1.zig b/libs/iguanatls/src/asn1.zig deleted file mode 100644 index 483bb7780bd1b75d5c4fe10d4112ca8ba6786ab3..0000000000000000000000000000000000000000 --- a/libs/iguanatls/src/asn1.zig +++ /dev/null @@ -1,622 +0,0 @@ -const std = @import("std"); -const BigInt = std.math.big.int.Const; -const mem = std.mem; -const Allocator = mem.Allocator; -const ArenaAllocator = std.heap.ArenaAllocator; - -// zig fmt: off -pub const Tag = enum(u8) { - bool = 0x01, - int = 0x02, - bit_string = 0x03, - octet_string = 0x04, - @"null" = 0x05, - object_identifier = 0x06, - utf8_string = 0x0c, - printable_string = 0x13, - ia5_string = 0x16, - utc_time = 0x17, - bmp_string = 0x1e, - sequence = 0x30, - set = 0x31, - // Bogus value - context_specific = 0xff, -}; -// zig fmt: on - -pub const ObjectIdentifier = struct { - data: [16]u32, - len: u8, -}; - -pub const BitString = struct { - data: []const u8, - bit_len: usize, -}; - -pub const Value = union(Tag) { - bool: bool, - int: BigInt, - bit_string: BitString, - octet_string: []const u8, - @"null", - // @TODO Make this []u32, owned? - object_identifier: ObjectIdentifier, - utf8_string: []const u8, - printable_string: []const u8, - ia5_string: []const u8, - utc_time: []const u8, - bmp_string: []const u16, - sequence: []const @This(), - set: []const @This(), - context_specific: struct { - child: *const Value, - number: u8, - }, - - pub fn deinit(self: @This(), alloc: *Allocator) void { - switch (self) { - .int => |i| alloc.free(i.limbs), - .bit_string => |bs| alloc.free(bs.data), - .octet_string, - .utf8_string, - .printable_string, - .ia5_string, - .utc_time, - => |s| alloc.free(s), - .bmp_string => |s| alloc.free(s), - .sequence, .set => |s| { - for (s) |c| { - c.deinit(alloc); - } - alloc.free(s); - }, - .context_specific => |cs| { - cs.child.deinit(alloc); - alloc.destroy(cs.child); - }, - else => {}, - } - } - - fn formatInternal( - self: Value, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - indents: usize, - writer: anytype, - ) @TypeOf(writer).Error!void { - try writer.writeByteNTimes(' ', indents); - switch (self) { - .bool => |b| try writer.print("BOOLEAN {}\n", .{b}), - .int => |i| { - try writer.writeAll("INTEGER "); - try i.format(fmt, options, writer); - try writer.writeByte('\n'); - }, - .bit_string => |bs| { - try writer.print("BIT STRING ({} bits) ", .{bs.bit_len}); - const bits_to_show = std.math.min(8 * 3, bs.bit_len); - const bytes = std.math.divCeil(usize, bits_to_show, 8) catch unreachable; - - var bit_idx: usize = 0; - var byte_idx: usize = 0; - while (byte_idx < bytes) : (byte_idx += 1) { - const byte = bs.data[byte_idx]; - var cur_bit_idx: u3 = 0; - while (bit_idx < bits_to_show) { - const mask = @as(u8, 0x80) >> cur_bit_idx; - try writer.print("{}", .{@boolToInt(byte & mask == mask)}); - cur_bit_idx += 1; - bit_idx += 1; - if (cur_bit_idx == 7) - break; - } - } - if (bits_to_show != bs.bit_len) - try writer.writeAll("..."); - try writer.writeByte('\n'); - }, - .octet_string => |s| try writer.print("OCTET STRING ({} bytes) {X}\n", .{ s.len, s }), - .@"null" => try writer.writeAll("NULL\n"), - .object_identifier => |oid| { - try writer.writeAll("OBJECT IDENTIFIER "); - var i: u8 = 0; - while (i < oid.len) : (i += 1) { - if (i != 0) try writer.writeByte('.'); - try writer.print("{}", .{oid.data[i]}); - } - try writer.writeByte('\n'); - }, - .utf8_string => |s| try writer.print("UTF8 STRING ({} bytes) {}\n", .{ s.len, s }), - .printable_string => |s| try writer.print("PRINTABLE STRING ({} bytes) {}\n", .{ s.len, s }), - .ia5_string => |s| try writer.print("IA5 STRING ({} bytes) {}\n", .{ s.len, s }), - .utc_time => |s| try writer.print("UTC TIME {}\n", .{s}), - .bmp_string => |s| try writer.print("BMP STRING ({} words) {}\n", .{ - s.len, - @ptrCast([*]const u16, s.ptr)[0 .. s.len * 2], - }), - .sequence => |children| { - try writer.print("SEQUENCE ({} elems)\n", .{children.len}); - for (children) |child| try child.formatInternal(fmt, options, indents + 2, writer); - }, - .set => |children| { - try writer.print("SET ({} elems)\n", .{children.len}); - for (children) |child| try child.formatInternal(fmt, options, indents + 2, writer); - }, - .context_specific => |cs| { - try writer.print("[{}]\n", .{cs.number}); - try cs.child.formatInternal(fmt, options, indents + 2, writer); - }, - } - } - - pub fn format(self: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - try self.formatInternal(fmt, options, 0, writer); - } -}; - -/// Distinguished encoding rules -pub const der = struct { - pub fn DecodeError(comptime Reader: type) type { - return Reader.Error || error{ - OutOfMemory, - EndOfStream, - InvalidLength, - InvalidTag, - InvalidContainerLength, - DoesNotMatchSchema, - }; - } - - fn DERReaderState(comptime Reader: type) type { - return struct { - der_reader: Reader, - length: usize, - idx: usize = 0, - }; - } - - fn DERReader(comptime Reader: type) type { - const S = struct { - pub fn read(state: *DERReaderState(Reader), buffer: []u8) DecodeError(Reader)!usize { - const out_bytes = std.math.min(buffer.len, state.length - state.idx); - const res = try state.der_reader.readAll(buffer[0..out_bytes]); - state.idx += res; - return res; - } - }; - - return std.io.Reader(*DERReaderState(Reader), DecodeError(Reader), S.read); - } - - pub fn parse_schema( - schema: anytype, - captures: anytype, - der_reader: anytype, - ) !void { - const res = try parse_schema_tag_len_internal(null, null, schema, captures, der_reader); - if (res != null) return error.DoesNotMatchSchema; - } - - pub fn parse_schema_tag_len( - existing_tag_byte: ?u8, - existing_length: ?usize, - schema: anytype, - captures: anytype, - der_reader: anytype, - ) !void { - const res = try parse_schema_tag_len_internal( - existing_tag_byte, - existing_length, - schema, - captures, - der_reader, - ); - if (res != null) return error.DoesNotMatchSchema; - } - - const TagLength = struct { - tag: u8, - length: usize, - }; - - pub fn parse_schema_tag_len_internal( - existing_tag_byte: ?u8, - existing_length: ?usize, - schema: anytype, - captures: anytype, - der_reader: anytype, - ) !?TagLength { - const Reader = @TypeOf(der_reader); - - const isEnumLit = comptime std.meta.trait.is(.EnumLiteral); - comptime var tag_idx = 0; - - const has_capture = comptime isEnumLit(@TypeOf(schema[tag_idx])) and schema[tag_idx] == .capture; - if (has_capture) tag_idx += 2; - - const is_optional = comptime isEnumLit(@TypeOf(schema[tag_idx])) and schema[tag_idx] == .optional; - if (is_optional) tag_idx += 1; - - const tag_literal = schema[tag_idx]; - comptime std.debug.assert(isEnumLit(@TypeOf(tag_literal))); - - const tag_byte = existing_tag_byte orelse (der_reader.readByte() catch |err| switch (err) { - error.EndOfStream => |e| return if (is_optional) null else error.EndOfStream, - else => |e| return e, - }); - - const length = existing_length orelse try parse_length(der_reader); - if (tag_literal == .sequence_of) { - if (tag_byte != @enumToInt(Tag.sequence)) { - if (is_optional) return TagLength{ .tag = tag_byte, .length = length }; - return error.InvalidTag; - } - - var curr_tag_length: ?TagLength = null; - const sub_schema = schema[tag_idx + 1]; - while (true) { - if (curr_tag_length == null) { - curr_tag_length = .{ - .tag = der_reader.readByte() catch |err| switch (err) { - error.EndOfStream => { - curr_tag_length = null; - break; - }, - else => |e| return e, - }, - .length = try parse_length(der_reader), - }; - } - - curr_tag_length = parse_schema_tag_len_internal( - curr_tag_length.?.tag, - curr_tag_length.?.length, - sub_schema, - captures, - der_reader, - ) catch |err| switch (err) { - error.DoesNotMatchSchema => break, - else => |e| return e, - }; - } - return curr_tag_length; - } else if (tag_literal == .any) { - if (!has_capture) { - try der_reader.skipBytes(length, .{}); - return null; - } - - var reader_state = DERReaderState(Reader){ - .der_reader = der_reader, - .idx = 0, - .length = length, - }; - var reader = DERReader(@TypeOf(der_reader)){ .context = &reader_state }; - const capture_context = captures[schema[1] * 2]; - const capture_action = captures[schema[1] * 2 + 1]; - try capture_action(capture_context, tag_byte, length, reader); - - // Skip remaining bytes - try der_reader.skipBytes(reader_state.length - reader_state.idx, .{}); - return null; - } else if (tag_literal == .context_specific) { - const cs_number = schema[tag_idx + 1]; - if (tag_byte & 0xC0 == 0x80 and tag_byte - 0xa0 == cs_number) { - if (!has_capture) { - if (schema.len > tag_idx + 2) { - return try parse_schema_tag_len_internal(null, null, schema[tag_idx + 2], captures, der_reader); - } - - try der_reader.skipBytes(length, .{}); - return null; - } - - var reader_state = DERReaderState(Reader){ - .der_reader = der_reader, - .idx = 0, - .length = length, - }; - var reader = DERReader(Reader){ .context = &reader_state }; - const capture_context = captures[schema[1] * 2]; - const capture_action = captures[schema[1] * 2 + 1]; - try capture_action(capture_context, tag_byte, length, reader); - - // Skip remaining bytes - try der_reader.skipBytes(reader_state.length - reader_state.idx, .{}); - return null; - } else if (is_optional) - return TagLength{ .tag = tag_byte, .length = length } - else - return error.DoesNotMatchSchema; - } - - const schema_tag: Tag = tag_literal; - const actual_tag = std.meta.intToEnum(Tag, tag_byte) catch return error.InvalidTag; - if (actual_tag != schema_tag) { - if (is_optional) return tag_byte; - return error.DoesNotMatchSchema; - } - - const single_seq = schema_tag == .sequence and schema.len == 1; - if ((!has_capture and schema_tag != .sequence) or (!has_capture and single_seq)) { - try der_reader.skipBytes(length, .{}); - return null; - } - - if (has_capture) { - var reader_state = DERReaderState(Reader){ - .der_reader = der_reader, - .idx = 0, - .length = length, - }; - var reader = DERReader(Reader){ .context = &reader_state }; - const capture_context = captures[schema[1] * 2]; - const capture_action = captures[schema[1] * 2 + 1]; - try capture_action(capture_context, tag_byte, length, reader); - - // Skip remaining bytes - try der_reader.skipBytes(reader_state.length - reader_state.idx, .{}); - return null; - } - - var cur_tag_length: ?TagLength = null; - const sub_schemas = schema[tag_idx + 1]; - comptime var i = 0; - inline while (i < sub_schemas.len) : (i += 1) { - const curr_tag = if (cur_tag_length) |tl| tl.tag else null; - const curr_length = if (cur_tag_length) |tl| tl.length else null; - cur_tag_length = try parse_schema_tag_len_internal(curr_tag, curr_length, sub_schemas[i], captures, der_reader); - } - return cur_tag_length; - } - - pub const EncodedLength = struct { - data: [@sizeOf(usize) + 1]u8, - len: usize, - - pub fn slice(self: @This()) []const u8 { - if (self.len == 1) return self.data[0..1]; - return self.data[0 .. 1 + self.len]; - } - }; - - pub fn encode_length(length: usize) EncodedLength { - var enc = EncodedLength{ .data = undefined, .len = 0 }; - if (length < 128) { - enc.data[0] = @truncate(u8, length); - enc.len = 1; - } else { - const bytes_needed = @intCast(u8, std.math.divCeil( - usize, - std.math.log2_int_ceil(usize, length), - 8, - ) catch unreachable); - enc.data[0] = bytes_needed | 0x80; - mem.copy( - u8, - enc.data[1 .. bytes_needed + 1], - mem.asBytes(&length)[0..bytes_needed], - ); - if (std.builtin.endian != .Big) { - mem.reverse(u8, enc.data[1 .. bytes_needed + 1]); - } - enc.len = bytes_needed; - } - return enc; - } - - fn parse_int_internal(alloc: *Allocator, bytes_read: *usize, der_reader: anytype) !BigInt { - const length = try parse_length_internal(bytes_read, der_reader); - const first_byte = try der_reader.readByte(); - if (first_byte == 0x0 and length > 1) { - // Positive number with highest bit set to 1 in the rest. - const limb_count = std.math.divCeil(usize, length - 1, @sizeOf(usize)) catch unreachable; - const limbs = try alloc.alloc(usize, limb_count); - std.mem.set(usize, limbs, 0); - errdefer alloc.free(limbs); - - var limb_ptr = @ptrCast([*]u8, limbs.ptr); - try der_reader.readNoEof(limb_ptr[0 .. length - 1]); - // We always reverse because the standard library big int expects little endian. - mem.reverse(u8, limb_ptr[0 .. length - 1]); - - bytes_read.* += length; - return BigInt{ .limbs = limbs, .positive = true }; - } - std.debug.assert(length != 0); - // Write first_byte - // Twos complement - const limb_count = std.math.divCeil(usize, length, @sizeOf(usize)) catch unreachable; - const limbs = try alloc.alloc(usize, limb_count); - std.mem.set(usize, limbs, 0); - errdefer alloc.free(limbs); - - var limb_ptr = @ptrCast([*]u8, limbs.ptr); - limb_ptr[0] = first_byte & ~@as(u8, 0x80); - try der_reader.readNoEof(limb_ptr[1..length]); - - // We always reverse because the standard library big int expects little endian. - mem.reverse(u8, limb_ptr[0..length]); - bytes_read.* += length; - return BigInt{ .limbs = limbs, .positive = (first_byte & 0x80) == 0x00 }; - } - - pub fn parse_int(alloc: *Allocator, der_reader: anytype) !BigInt { - var bytes: usize = undefined; - return try parse_int_internal(alloc, &bytes, der_reader); - } - - pub fn parse_length(der_reader: anytype) !usize { - var bytes: usize = 0; - return try parse_length_internal(&bytes, der_reader); - } - - fn parse_length_internal(bytes_read: *usize, der_reader: anytype) !usize { - const first_byte = try der_reader.readByte(); - bytes_read.* += 1; - if (first_byte & 0x80 == 0x00) { - // 1 byte value - return first_byte; - } - const length = @truncate(u7, first_byte); - if (length > @sizeOf(usize)) - @panic("DER length does not fit in usize"); - - var res_buf = std.mem.zeroes([@sizeOf(usize)]u8); - try der_reader.readNoEof(res_buf[0..length]); - bytes_read.* += length; - - if (std.builtin.endian != .Big) { - mem.reverse(u8, res_buf[0..length]); - } - return mem.bytesToValue(usize, &res_buf); - } - - fn parse_value_with_tag_byte( - tag_byte: u8, - alloc: *Allocator, - bytes_read: *usize, - der_reader: anytype, - ) DecodeError(@TypeOf(der_reader))!Value { - const tag = std.meta.intToEnum(Tag, tag_byte) catch { - // tag starts with '0b10...', this is the context specific class. - if (tag_byte & 0xC0 == 0x80) { - const length = try parse_length_internal(bytes_read, der_reader); - var cur_read_bytes: usize = 0; - var child = try alloc.create(Value); - errdefer alloc.destroy(child); - - child.* = try parse_value_internal(alloc, &cur_read_bytes, der_reader); - if (cur_read_bytes != length) - return error.InvalidContainerLength; - bytes_read.* += length; - return Value{ .context_specific = .{ .child = child, .number = tag_byte - 0xa0 } }; - } - - return error.InvalidTag; - }; - switch (tag) { - .bool => { - if ((try der_reader.readByte()) != 0x1) - return error.InvalidLength; - defer bytes_read.* += 2; - return Value{ .bool = (try der_reader.readByte()) != 0x0 }; - }, - .int => return Value{ .int = try parse_int_internal(alloc, bytes_read, der_reader) }, - .bit_string => { - const length = try parse_length_internal(bytes_read, der_reader); - const unused_bits = try der_reader.readByte(); - std.debug.assert(unused_bits < 8); - const bit_count = (length - 1) * 8 - unused_bits; - const bit_memory = try alloc.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable); - errdefer alloc.free(bit_memory); - try der_reader.readNoEof(bit_memory[0 .. length - 1]); - - bytes_read.* += length; - return Value{ .bit_string = .{ .data = bit_memory, .bit_len = bit_count } }; - }, - .octet_string, .utf8_string, .printable_string, .utc_time, .ia5_string => { - const length = try parse_length_internal(bytes_read, der_reader); - const str_mem = try alloc.alloc(u8, length); - try der_reader.readNoEof(str_mem); - bytes_read.* += length; - return @as(Value, switch (tag) { - .octet_string => .{ .octet_string = str_mem }, - .utf8_string => .{ .utf8_string = str_mem }, - .printable_string => .{ .printable_string = str_mem }, - .utc_time => .{ .utc_time = str_mem }, - .ia5_string => .{ .ia5_string = str_mem }, - else => unreachable, - }); - }, - .@"null" => { - std.debug.assert((try parse_length_internal(bytes_read, der_reader)) == 0x00); - return .@"null"; - }, - .object_identifier => { - const length = try parse_length_internal(bytes_read, der_reader); - const first_byte = try der_reader.readByte(); - var ret = Value{ .object_identifier = .{ .data = undefined, .len = 0 } }; - ret.object_identifier.data[0] = first_byte / 40; - ret.object_identifier.data[1] = first_byte % 40; - - var out_idx: u8 = 2; - var i: usize = 0; - while (i < length - 1) { - var current_value: u32 = 0; - var current_byte = try der_reader.readByte(); - i += 1; - while (current_byte & 0x80 == 0x80) : (i += 1) { - // Increase the base of the previous bytes - current_value *= 128; - // Add the current byte in base 128 - current_value += @as(u32, current_byte & ~@as(u8, 0x80)) * 128; - current_byte = try der_reader.readByte(); - } else { - current_value += current_byte; - } - ret.object_identifier.data[out_idx] = current_value; - out_idx += 1; - } - ret.object_identifier.len = out_idx; - std.debug.assert(out_idx <= 16); - bytes_read.* += length; - return ret; - }, - .bmp_string => { - const length = try parse_length_internal(bytes_read, der_reader); - const str_mem = try alloc.alloc(u16, @divExact(length, 2)); - errdefer alloc.free(str_mem); - - for (str_mem) |*wide_char| { - wide_char.* = try der_reader.readIntBig(u16); - } - bytes_read.* += length; - return Value{ .bmp_string = str_mem }; - }, - .sequence, .set => { - const length = try parse_length_internal(bytes_read, der_reader); - var cur_read_bytes: usize = 0; - var arr = std.ArrayList(Value).init(alloc); - errdefer arr.deinit(); - - while (cur_read_bytes < length) { - (try arr.addOne()).* = try parse_value_internal(alloc, &cur_read_bytes, der_reader); - } - if (cur_read_bytes != length) - return error.InvalidContainerLength; - bytes_read.* += length; - - return @as(Value, switch (tag) { - .sequence => .{ .sequence = arr.toOwnedSlice() }, - .set => .{ .set = arr.toOwnedSlice() }, - else => unreachable, - }); - }, - .context_specific => unreachable, - } - } - - fn parse_value_internal(alloc: *Allocator, bytes_read: *usize, der_reader: anytype) DecodeError(@TypeOf(der_reader))!Value { - const tag_byte = try der_reader.readByte(); - bytes_read.* += 1; - return try parse_value_with_tag_byte(tag_byte, alloc, bytes_read, der_reader); - } - - pub fn parse_value(alloc: *Allocator, der_reader: anytype) DecodeError(@TypeOf(der_reader))!Value { - var read: usize = 0; - return try parse_value_internal(alloc, &read, der_reader); - } -}; - -test "der.parse_value" { - const github_der = @embedFile("../test/github.der"); - var fbs = std.io.fixedBufferStream(github_der); - - var arena = ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - - _ = try der.parse_value(&arena.allocator, fbs.reader()); -} diff --git a/libs/iguanatls/src/ciphersuites.zig b/libs/iguanatls/src/ciphersuites.zig deleted file mode 100644 index 38c0c77915ed9eda33162e979bbbae0832a78b2a..0000000000000000000000000000000000000000 --- a/libs/iguanatls/src/ciphersuites.zig +++ /dev/null @@ -1,617 +0,0 @@ -const std = @import("std"); -const mem = std.mem; - -usingnamespace @import("crypto.zig"); -const Chacha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305; -const Aes128Gcm = std.crypto.aead.aes_gcm.Aes128Gcm; - -const main = @import("main.zig"); -const alert_byte_to_error = main.alert_byte_to_error; -const record_tag_length = main.record_tag_length; -const record_length = main.record_length; - -pub const suites = struct { - pub const ECDHE_RSA_Chacha20_Poly1305 = struct { - pub const name = "ECDHE-RSA-CHACHA20-POLY1305"; - pub const tag = 0xCCA8; - pub const key_exchange = .ecdhe; - pub const hash = .sha256; - - pub const Keys = struct { - client_key: [32]u8, - server_key: [32]u8, - client_iv: [12]u8, - server_iv: [12]u8, - }; - - pub const State = union(enum) { - none, - in_record: struct { - left: usize, - context: ChaCha20Stream.BlockVec, - idx: usize, - buf: [64]u8, - }, - }; - pub const default_state: State = .none; - - pub fn raw_write( - comptime buffer_size: usize, - rand: *std.rand.Random, - key_data: anytype, - writer: anytype, - prefix: [3]u8, - seq: u64, - buffer: []const u8, - ) !void { - std.debug.assert(buffer.len <= buffer_size); - try writer.writeAll(&prefix); - try writer.writeIntBig(u16, @intCast(u16, buffer.len + 16)); - - var additional_data: [13]u8 = undefined; - mem.writeIntBig(u64, additional_data[0..8], seq); - additional_data[8..11].* = prefix; - mem.writeIntBig(u16, additional_data[11..13], @intCast(u16, buffer.len)); - - var encrypted_data: [buffer_size]u8 = undefined; - var tag_data: [16]u8 = undefined; - - var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8); - mem.writeIntBig(u64, nonce[4..12], seq); - for (nonce) |*n, i| { - n.* ^= key_data.client_iv(@This())[i]; - } - - Chacha20Poly1305.encrypt( - encrypted_data[0..buffer.len], - &tag_data, - buffer, - &additional_data, - nonce, - key_data.client_key(@This()).*, - ); - try writer.writeAll(encrypted_data[0..buffer.len]); - try writer.writeAll(&tag_data); - } - - pub fn check_verify_message( - key_data: anytype, - length: usize, - reader: anytype, - verify_message: [16]u8, - ) !bool { - if (length != 32) - return false; - - var msg_in: [32]u8 = undefined; - try reader.readNoEof(&msg_in); - - const additional_data: [13]u8 = ([1]u8{0} ** 8) ++ [5]u8{ 0x16, 0x03, 0x03, 0x00, 0x10 }; - var decrypted: [16]u8 = undefined; - Chacha20Poly1305.decrypt( - &decrypted, - msg_in[0..16], - msg_in[16..].*, - &additional_data, - key_data.server_iv(@This()).*, - key_data.server_key(@This()).*, - ) catch return false; - - return mem.eql(u8, &decrypted, &verify_message); - } - - pub fn read( - comptime buf_size: usize, - state: *State, - key_data: anytype, - reader: anytype, - server_seq: *u64, - buffer: []u8, - ) !usize { - switch (state.*) { - .none => { - const tag_length = record_tag_length(reader) catch |err| switch (err) { - error.EndOfStream => return 0, - else => |e| return e, - }; - if (tag_length.length < 16) - return error.ServerMalformedResponse; - const len = tag_length.length - 16; - - if ((tag_length.tag != 0x17 and tag_length.tag != 0x15) or - (tag_length.tag == 0x15 and len != 2)) - { - return error.ServerMalformedResponse; - } - - const curr_bytes = if (tag_length.tag == 0x15) - 2 - else - std.math.min(std.math.min(len, buf_size), buffer.len); - - var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8); - mem.writeIntBig(u64, nonce[4..12], server_seq.*); - for (nonce) |*n, i| { - n.* ^= key_data.server_iv(@This())[i]; - } - - var c: [4]u32 = undefined; - c[0] = 1; - c[1] = mem.readIntLittle(u32, nonce[0..4]); - c[2] = mem.readIntLittle(u32, nonce[4..8]); - c[3] = mem.readIntLittle(u32, nonce[8..12]); - const server_key = keyToWords(key_data.server_key(@This()).*); - var context = ChaCha20Stream.initContext(server_key, c); - var idx: usize = 0; - var buf: [64]u8 = undefined; - - if (tag_length.tag == 0x15) { - var encrypted: [2]u8 = undefined; - reader.readNoEof(&encrypted) catch |err| switch (err) { - error.EndOfStream => return error.ServerMalformedResponse, - else => |e| return e, - }; - var result: [2] u8 = undefined; - ChaCha20Stream.chacha20Xor( - &result, - &encrypted, - server_key, - &context, - &idx, - &buf, - ); - reader.skipBytes(16, .{}) catch |err| switch (err) { - error.EndOfStream => return error.ServerMalformedResponse, - else => |e| return e, - }; - server_seq.* += 1; - // CloseNotify - if (result[1] == 0) - return 0; - return alert_byte_to_error(result[1]); - } else if (tag_length.tag == 0x17) { - // Partially decrypt the data. - var encrypted: [buf_size]u8 = undefined; - const actually_read = try reader.read(encrypted[0..curr_bytes]); - - ChaCha20Stream.chacha20Xor( - buffer[0..actually_read], - encrypted[0..actually_read], - server_key, - &context, - &idx, - &buf, - ); - if (actually_read < len) { - state.* = .{ - .in_record = .{ - .left = len - actually_read, - .context = context, - .idx = idx, - .buf = buf, - }, - }; - } else { - // @TODO Verify Poly1305. - reader.skipBytes(16, .{}) catch |err| switch (err) { - error.EndOfStream => return error.ServerMalformedResponse, - else => |e| return e, - }; - server_seq.* += 1; - } - return actually_read; - } else unreachable; - }, - .in_record => |*record_info| { - const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left); - // Partially decrypt the data. - var encrypted: [buf_size]u8 = undefined; - const actually_read = try reader.read(encrypted[0..curr_bytes]); - ChaCha20Stream.chacha20Xor( - buffer[0..actually_read], - encrypted[0..actually_read], - keyToWords(key_data.server_key(@This()).*), - &record_info.context, - &record_info.idx, - &record_info.buf, - ); - - record_info.left -= actually_read; - if (record_info.left == 0) { - // @TODO Verify Poly1305. - reader.skipBytes(16, .{}) catch |err| switch (err) { - error.EndOfStream => return error.ServerMalformedResponse, - else => |e| return e, - }; - state.* = .none; - server_seq.* += 1; - } - return actually_read; - }, - } - } - }; - - pub const ECDHE_RSA_AES128_GCM_SHA256 = struct { - pub const name = "ECDHE-RSA-AES128-GCM-SHA256"; - pub const tag = 0xC02F; - pub const key_exchange = .ecdhe; - pub const hash = .sha256; - - pub const Keys = struct { - client_key: [16]u8, - server_key: [16]u8, - client_iv: [4]u8, - server_iv: [4]u8, - }; - - const Aes = std.crypto.core.aes.Aes128; - pub const State = union(enum) { - none, - in_record: struct { - left: usize, - aes: @typeInfo(@TypeOf(Aes.initEnc)).Fn.return_type.?, - // ctr state - counterInt: u128, - idx: usize, - }, - }; - pub const default_state: State = .none; - - pub fn check_verify_message( - key_data: anytype, - length: usize, - reader: anytype, - verify_message: [16]u8, - ) !bool { - if (length != 40) - return false; - - var iv: [12]u8 = undefined; - iv[0..4].* = key_data.server_iv(@This()).*; - try reader.readNoEof(iv[4..12]); - - var msg_in: [32]u8 = undefined; - try reader.readNoEof(&msg_in); - - const additional_data: [13]u8 = ([1]u8{0} ** 8) ++ [5]u8{ 0x16, 0x03, 0x03, 0x00, 0x10 }; - var decrypted: [16]u8 = undefined; - Aes128Gcm.decrypt( - &decrypted, - msg_in[0..16], - msg_in[16..].*, - &additional_data, - iv, - key_data.server_key(@This()).*, - ) catch return false; - - return mem.eql(u8, &decrypted, &verify_message); - } - - pub fn raw_write( - comptime buffer_size: usize, - rand: *std.rand.Random, - key_data: anytype, - writer: anytype, - prefix: [3]u8, - seq: u64, - buffer: []const u8, - ) !void { - std.debug.assert(buffer.len <= buffer_size); - var iv: [12]u8 = undefined; - iv[0..4].* = key_data.client_iv(@This()).*; - rand.bytes(iv[4..12]); - - var additional_data: [13]u8 = undefined; - mem.writeIntBig(u64, additional_data[0..8], seq); - additional_data[8..11].* = prefix; - mem.writeIntBig(u16, additional_data[11..13], @intCast(u16, buffer.len)); - - try writer.writeAll(&prefix); - try writer.writeIntBig(u16, @intCast(u16, buffer.len + 24)); - try writer.writeAll(iv[4..12]); - - var encrypted_data: [buffer_size]u8 = undefined; - var tag_data: [16]u8 = undefined; - - Aes128Gcm.encrypt( - encrypted_data[0..buffer.len], - &tag_data, - buffer, - &additional_data, - iv, - key_data.client_key(@This()).*, - ); - try writer.writeAll(encrypted_data[0..buffer.len]); - try writer.writeAll(&tag_data); - } - - pub fn read( - comptime buf_size: usize, - state: *State, - key_data: anytype, - reader: anytype, - server_seq: *u64, - buffer: []u8, - ) !usize { - switch (state.*) { - .none => { - const tag_length = record_tag_length(reader) catch |err| switch (err) { - error.EndOfStream => return 0, - else => |e| return e, - }; - if (tag_length.length < 24) - return error.ServerMalformedResponse; - const len = tag_length.length - 24; - - if ((tag_length.tag != 0x17 and tag_length.tag != 0x15) or - (tag_length.tag == 0x15 and len != 2)) - { - return error.ServerMalformedResponse; - } - - const curr_bytes = if (tag_length.tag == 0x15) - 2 - else - std.math.min(std.math.min(len, buf_size), buffer.len); - - var iv: [12]u8 = undefined; - iv[0..4].* = key_data.server_iv(@This()).*; - reader.readNoEof(iv[4..12]) catch |err| switch (err) { - error.EndOfStream => return 0, - else => |e| return e, - }; - - const aes = Aes.initEnc(key_data.server_key(@This()).*); - - var j: [16]u8 = undefined; - mem.copy(u8, j[0..12], iv[0..]); - mem.writeIntBig(u32, j[12..][0..4], 2); - - var counterInt = mem.readInt(u128, &j, .Big); - var idx: usize = 0; - - if (tag_length.tag == 0x15) { - var encrypted: [2]u8 = undefined; - reader.readNoEof(&encrypted) catch |err| switch (err) { - error.EndOfStream => return error.ServerMalformedResponse, - else => |e| return e, - }; - - var result: [2]u8 = undefined; - ctr( - @TypeOf(aes), - aes, - &result, - &encrypted, - &counterInt, - &idx, - .Big, - ); - reader.skipBytes(16, .{}) catch |err| switch (err) { - error.EndOfStream => return error.ServerMalformedResponse, - else => |e| return e, - }; - server_seq.* += 1; - // CloseNotify - if (result[1] == 0) - return 0; - return alert_byte_to_error(result[1]); - } else if (tag_length.tag == 0x17) { - // Partially decrypt the data. - var encrypted: [buf_size]u8 = undefined; - const actually_read = try reader.read(encrypted[0..curr_bytes]); - - ctr( - @TypeOf(aes), - aes, - buffer[0..actually_read], - encrypted[0..actually_read], - &counterInt, - &idx, - .Big, - ); - - if (actually_read < len) { - state.* = .{ - .in_record = .{ - .left = len - actually_read, - .aes = aes, - .counterInt = counterInt, - .idx = idx, - }, - }; - } else { - // @TODO Verify the message - reader.skipBytes(16, .{}) catch |err| switch (err) { - error.EndOfStream => return error.ServerMalformedResponse, - else => |e| return e, - }; - server_seq.* += 1; - } - return actually_read; - } else unreachable; - }, - .in_record => |*record_info| { - const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left); - // Partially decrypt the data. - var encrypted: [buf_size]u8 = undefined; - const actually_read = try reader.read(encrypted[0..curr_bytes]); - - ctr( - @TypeOf(record_info.aes), - record_info.aes, - buffer[0..actually_read], - encrypted[0..actually_read], - &record_info.counterInt, - &record_info.idx, - .Big, - ); - record_info.left -= actually_read; - if (record_info.left == 0) { - // @TODO Verify Poly1305. - reader.skipBytes(16, .{}) catch |err| switch (err) { - error.EndOfStream => return error.ServerMalformedResponse, - else => |e| return e, - }; - state.* = .none; - server_seq.* += 1; - } - return actually_read; - }, - } - } - }; - - pub const all = &[_]type{ ECDHE_RSA_Chacha20_Poly1305, ECDHE_RSA_AES128_GCM_SHA256 }; -}; - -fn key_field_width(comptime T: type, comptime field: anytype) ?usize { - if (!@hasField(T, @tagName(field))) - return null; - - const field_info = std.meta.fieldInfo(T, field); - if (!comptime std.meta.trait.is(.Array)(field_info.field_type) or std.meta.Elem(field_info.field_type) != u8) - @compileError("Field '" ++ field ++ "' of type '" ++ @typeName(T) ++ "' should be an array of u8."); - - return @typeInfo(field_info.field_type).Array.len; -} - -pub fn key_data_size(comptime ciphersuites: anytype) usize { - var max: usize = 0; - for (ciphersuites) |cs| { - const curr = (key_field_width(cs.Keys, .client_mac) orelse 0) + - (key_field_width(cs.Keys, .server_mac) orelse 0) + - key_field_width(cs.Keys, .client_key).? + - key_field_width(cs.Keys, .server_key).? + - key_field_width(cs.Keys, .client_iv).? + - key_field_width(cs.Keys, .server_iv).?; - if (curr > max) - max = curr; - } - return max; -} - -pub fn KeyData(comptime ciphersuites: anytype) type { - return struct { - data: [key_data_size(ciphersuites)]u8, - - pub fn client_mac(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_mac) orelse 0]u8 { - return self.data[0..comptime (key_field_width(cs.Keys, .client_mac) orelse 0)]; - } - - pub fn server_mac(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_mac) orelse 0]u8 { - const start = key_field_width(cs.Keys, .client_mac) orelse 0; - return self.data[start..][0..comptime (key_field_width(cs.Keys, .server_mac) orelse 0)]; - } - - pub fn client_key(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_key).?]u8 { - const start = (key_field_width(cs.Keys, .client_mac) orelse 0) + - (key_field_width(cs.Keys, .server_mac) orelse 0); - return self.data[start..][0..comptime key_field_width(cs.Keys, .client_key).?]; - } - - pub fn server_key(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_key).?]u8 { - const start = (key_field_width(cs.Keys, .client_mac) orelse 0) + - (key_field_width(cs.Keys, .server_mac) orelse 0) + - key_field_width(cs.Keys, .client_key).?; - return self.data[start..][0..comptime key_field_width(cs.Keys, .server_key).?]; - } - - pub fn client_iv(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_iv).?]u8 { - const start = (key_field_width(cs.Keys, .client_mac) orelse 0) + - (key_field_width(cs.Keys, .server_mac) orelse 0) + - key_field_width(cs.Keys, .client_key).? + - key_field_width(cs.Keys, .server_key).?; - return self.data[start..][0..comptime key_field_width(cs.Keys, .client_iv).?]; - } - - pub fn server_iv(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_iv).?]u8 { - const start = (key_field_width(cs.Keys, .client_mac) orelse 0) + - (key_field_width(cs.Keys, .server_mac) orelse 0) + - key_field_width(cs.Keys, .client_key).? + - key_field_width(cs.Keys, .server_key).? + - key_field_width(cs.Keys, .client_iv).?; - return self.data[start..][0..comptime key_field_width(cs.Keys, .server_iv).?]; - } - }; -} - -pub fn key_expansion( - comptime ciphersuites: anytype, - tag: u16, - context: anytype, - comptime next_32_bytes: anytype, -) KeyData(ciphersuites) { - var res: KeyData(ciphersuites) = undefined; - inline for (ciphersuites) |cs| { - if (cs.tag == tag) { - var chunk: [32]u8 = undefined; - next_32_bytes(context, 0, &chunk); - comptime var chunk_idx = 1; - comptime var data_cursor = 0; - comptime var chunk_cursor = 0; - - const fields = .{ - .client_mac, .server_mac, - .client_key, .server_key, - .client_iv, .server_iv, - }; - inline for (fields) |field| { - if (chunk_cursor == 32) { - next_32_bytes(context, chunk_idx, &chunk); - chunk_idx += 1; - chunk_cursor = 0; - } - - const field_width = comptime (key_field_width(cs.Keys, field) orelse 0); - const first_read = comptime std.math.min(32 - chunk_cursor, field_width); - const second_read = field_width - first_read; - - res.data[data_cursor..][0..first_read].* = chunk[chunk_cursor..][0..first_read].*; - data_cursor += first_read; - chunk_cursor += first_read; - - if (second_read != 0) { - next_32_bytes(context, chunk_idx, &chunk); - chunk_idx += 1; - res.data[data_cursor..][0..second_read].* = chunk[chunk_cursor..][0..second_read].*; - data_cursor += second_read; - chunk_cursor = second_read; - comptime std.debug.assert(chunk_cursor != 32); - } - } - - return res; - } - } - unreachable; -} - -pub fn ClientState(comptime ciphersuites: anytype) type { - var fields: [ciphersuites.len]std.builtin.TypeInfo.UnionField = undefined; - for (ciphersuites) |cs, i| { - fields[i] = .{ - .name = cs.name, - .field_type = cs.State, - .alignment = if (@sizeOf(cs.State) > 0) @alignOf(cs.State) else 0, - }; - } - return @Type(.{ - .Union = .{ - .layout = .Extern, - .tag_type = null, - .fields = &fields, - .decls = &[0]std.builtin.TypeInfo.Declaration{}, - }, - }); -} - -pub fn client_state_default(comptime ciphersuites: anytype, tag: u16) ClientState(ciphersuites) { - inline for (ciphersuites) |cs| { - if (cs.tag == tag) { - return @unionInit(ClientState(ciphersuites), cs.name, cs.default_state); - } - } - unreachable; -} diff --git a/libs/iguanatls/src/crypto.zig b/libs/iguanatls/src/crypto.zig deleted file mode 100644 index 25553c318157a594a8cf46f3f36e947e48479c42..0000000000000000000000000000000000000000 --- a/libs/iguanatls/src/crypto.zig +++ /dev/null @@ -1,984 +0,0 @@ -const std = @import("std"); -const mem = std.mem; - -// TODO See stdlib, this is a modified non vectorized implementation -pub const ChaCha20Stream = struct { - const math = std.math; - pub const BlockVec = [16]u32; - - pub fn initContext(key: [8]u32, d: [4]u32) BlockVec { - const c = "expand 32-byte k"; - const constant_le = comptime [4]u32{ - mem.readIntLittle(u32, c[0..4]), - mem.readIntLittle(u32, c[4..8]), - mem.readIntLittle(u32, c[8..12]), - mem.readIntLittle(u32, c[12..16]), - }; - return BlockVec{ - constant_le[0], constant_le[1], constant_le[2], constant_le[3], - key[0], key[1], key[2], key[3], - key[4], key[5], key[6], key[7], - d[0], d[1], d[2], d[3], - }; - } - - const QuarterRound = struct { - a: usize, - b: usize, - c: usize, - d: usize, - }; - - fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { - return QuarterRound{ - .a = a, - .b = b, - .c = c, - .d = d, - }; - } - - inline fn chacha20Core(x: *BlockVec, input: BlockVec) void { - x.* = input; - - const rounds = comptime [_]QuarterRound{ - Rp(0, 4, 8, 12), - Rp(1, 5, 9, 13), - Rp(2, 6, 10, 14), - Rp(3, 7, 11, 15), - Rp(0, 5, 10, 15), - Rp(1, 6, 11, 12), - Rp(2, 7, 8, 13), - Rp(3, 4, 9, 14), - }; - - comptime var j: usize = 0; - inline while (j < 20) : (j += 2) { - inline for (rounds) |r| { - x[r.a] +%= x[r.b]; - x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16)); - x[r.c] +%= x[r.d]; - x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12)); - x[r.a] +%= x[r.b]; - x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8)); - x[r.c] +%= x[r.d]; - x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7)); - } - } - } - - inline fn hashToBytes(out: *[64]u8, x: BlockVec) void { - var i: usize = 0; - while (i < 4) : (i += 1) { - mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]); - mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]); - mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]); - mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]); - } - } - - inline fn contextFeedback(x: *BlockVec, ctx: BlockVec) void { - var i: usize = 0; - while (i < 16) : (i += 1) { - x[i] +%= ctx[i]; - } - } - - // TODO: Optimize this - pub fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, ctx: *BlockVec, idx: *usize, buf: *[64]u8) void { - var x: BlockVec = undefined; - - const start_idx = idx.*; - var i: usize = 0; - while (i < in.len) { - if (idx.* % 64 == 0) { - if (idx.* != 0) { - ctx.*[12] += 1; - } - chacha20Core(x[0..], ctx.*); - contextFeedback(&x, ctx.*); - hashToBytes(buf, x); - } - - out[i] = in[i] ^ buf[idx.* % 64]; - - i += 1; - idx.* += 1; - } - } -}; - -pub fn keyToWords(key: [32]u8) [8]u32 { - var k: [8]u32 = undefined; - var i: usize = 0; - while (i < 8) : (i += 1) { - k[i] = mem.readIntLittle(u32, key[i * 4 ..][0..4]); - } - return k; -} - -// See std.crypto.core.modes.ctr -/// This mode creates a key stream by encrypting an incrementing counter using a block cipher, and adding it to the source material. -pub fn ctr( - comptime BlockCipher: anytype, - block_cipher: BlockCipher, - dst: []u8, - src: []const u8, - counterInt: *u128, - idx: *usize, - endian: comptime std.builtin.Endian, -) void { - std.debug.assert(dst.len >= src.len); - const block_length = BlockCipher.block_length; - var cur_idx: usize = 0; - - const offset = idx.* % block_length; - if (offset != 0) { - const part_len = std.math.min(block_length - offset, src.len); - - var counter: [BlockCipher.block_length]u8 = undefined; - mem.writeInt(u128, &counter, counterInt.*, endian); - var pad = [_]u8{0} ** block_length; - mem.copy(u8, pad[offset..], src[0..part_len]); - block_cipher.xor(&pad, &pad, counter); - mem.copy(u8, dst[0..part_len], pad[offset..][0..part_len]); - - cur_idx += part_len; - idx.* += part_len; - if (idx.* % block_length == 0) - counterInt.* += 1; - } - - const start_idx = cur_idx; - const remaining = src.len - cur_idx; - cur_idx = 0; - - const parallel_count = BlockCipher.block.parallel.optimal_parallel_blocks; - const wide_block_length = parallel_count * 16; - if (remaining >= wide_block_length) { - var counters: [parallel_count * 16]u8 = undefined; - while (cur_idx + wide_block_length <= remaining) : (cur_idx += wide_block_length) { - comptime var j = 0; - inline while (j < parallel_count) : (j += 1) { - mem.writeInt(u128, counters[j * 16 .. j * 16 + 16], counterInt.*, endian); - counterInt.* +%= 1; - } - 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); - idx.* += wide_block_length; - } - } - while (cur_idx + block_length <= remaining) : (cur_idx += block_length) { - var counter: [BlockCipher.block_length]u8 = undefined; - mem.writeInt(u128, &counter, counterInt.*, endian); - counterInt.* +%= 1; - 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); - idx.* += block_length; - } - if (cur_idx < remaining) { - std.debug.assert(idx.* % block_length == 0); - var counter: [BlockCipher.block_length]u8 = undefined; - mem.writeInt(u128, &counter, counterInt.*, endian); - - var pad = [_]u8{0} ** block_length; - mem.copy(u8, &pad, src[start_idx..][cur_idx..]); - block_cipher.xor(&pad, &pad, counter); - mem.copy(u8, dst[cur_idx..], pad[0 .. remaining - cur_idx]); - - idx.* += remaining - cur_idx; - if (idx.* % block_length == 0) - counterInt.* +%= 1; - } -} - -// Ported from BearSSL's ec_prime_i31 engine -pub const ecc = struct { - pub const SECP384R1 = struct { - pub const point_len = 96; - - const order = [point_len / 2]u8{ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF, - 0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A, - 0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73, - }; - - const P = [_]u32{ - 0x0000018C, 0x7FFFFFFF, 0x00000001, 0x00000000, - 0x7FFFFFF8, 0x7FFFFFEF, 0x7FFFFFFF, 0x7FFFFFFF, - 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, - 0x7FFFFFFF, 0x00000FFF, - }; - const R2 = [_]u32{ - 0x0000018C, 0x00000000, 0x00000080, 0x7FFFFE00, - 0x000001FF, 0x00000800, 0x00000000, 0x7FFFE000, - 0x00001FFF, 0x00008000, 0x00008000, 0x00000000, - 0x00000000, 0x00000000, - }; - const B = [_]u32{ - 0x0000018C, 0x6E666840, 0x070D0392, 0x5D810231, - 0x7651D50C, 0x17E218D6, 0x1B192002, 0x44EFE441, - 0x3A524E2B, 0x2719BA5F, 0x41F02209, 0x36C5643E, - 0x5813EFFE, 0x000008A5, - }; - - const base_point = [point_len]u8{ - 0xAA, 0x87, 0xCA, 0x22, 0xBE, 0x8B, 0x05, 0x37, - 0x8E, 0xB1, 0xC7, 0x1E, 0xF3, 0x20, 0xAD, 0x74, - 0x6E, 0x1D, 0x3B, 0x62, 0x8B, 0xA7, 0x9B, 0x98, - 0x59, 0xF7, 0x41, 0xE0, 0x82, 0x54, 0x2A, 0x38, - 0x55, 0x02, 0xF2, 0x5D, 0xBF, 0x55, 0x29, 0x6C, - 0x3A, 0x54, 0x5E, 0x38, 0x72, 0x76, 0x0A, 0xB7, - 0x36, 0x17, 0xDE, 0x4A, 0x96, 0x26, 0x2C, 0x6F, - 0x5D, 0x9E, 0x98, 0xBF, 0x92, 0x92, 0xDC, 0x29, - 0xF8, 0xF4, 0x1D, 0xBD, 0x28, 0x9A, 0x14, 0x7C, - 0xE9, 0xDA, 0x31, 0x13, 0xB5, 0xF0, 0xB8, 0xC0, - 0x0A, 0x60, 0xB1, 0xCE, 0x1D, 0x7E, 0x81, 0x9D, - 0x7A, 0x43, 0x1D, 0x7C, 0x90, 0xEA, 0x0E, 0x5F, - }; - - comptime { - std.debug.assert((P[0] - (P[0] >> 5) + 7) >> 2 == point_len + 1); - } - }; - - pub const SECP256R1 = struct { - pub const point_len = 64; - - const order = [point_len / 2]u8{ - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, - 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, - }; - - const P = [_]u32{ - 0x00000108, 0x7FFFFFFF, - 0x7FFFFFFF, 0x7FFFFFFF, - 0x00000007, 0x00000000, - 0x00000000, 0x00000040, - 0x7FFFFF80, 0x000000FF, - }; - const R2 = [_]u32{ - 0x00000108, 0x00014000, - 0x00018000, 0x00000000, - 0x7FF40000, 0x7FEFFFFF, - 0x7FF7FFFF, 0x7FAFFFFF, - 0x005FFFFF, 0x00000000, - }; - const B = [_]u32{ - 0x00000108, 0x6FEE1803, - 0x6229C4BD, 0x21B139BE, - 0x327150AA, 0x3567802E, - 0x3F7212ED, 0x012E4355, - 0x782DD38D, 0x0000000E, - }; - - const base_point = [point_len]u8{ - 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, - 0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40, 0xF2, - 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, - 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96, - 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, - 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, - 0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE, - 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5, - }; - - comptime { - std.debug.assert((P[0] - (P[0] >> 5) + 7) >> 2 == point_len + 1); - } - }; - - fn jacobian_len(comptime Curve: type) usize { - return @divTrunc(Curve.order.len * 8 + 61, 31); - } - - fn Jacobian(comptime Curve: type) type { - return [3][jacobian_len(Curve)]u32; - } - - fn zero_jacobian(comptime Curve: type) Jacobian(Curve) { - var result = std.mem.zeroes(Jacobian(Curve)); - result[0][0] = Curve.P[0]; - result[1][0] = Curve.P[0]; - result[2][0] = Curve.P[0]; - return result; - } - - pub fn scalarmult( - comptime Curve: type, - point: [Curve.point_len]u8, - k: []const u8, - ) ![Curve.point_len]u8 { - var P: Jacobian(Curve) = undefined; - var res: u32 = decode_to_jacobian(Curve, &P, point); - point_mul(Curve, &P, k); - var out: [Curve.point_len]u8 = undefined; - encode_from_jacobian(Curve, &out, P); - if (res == 0) - return error.MultiplicationFailed; - return out; - } - - pub fn KeyPair(comptime Curve: type) type { - return struct { - public_key: [Curve.point_len]u8, - secret_key: [Curve.point_len / 2]u8, - }; - } - - pub fn make_key_pair(comptime Curve: type, rand_bytes: [Curve.point_len / 2]u8) KeyPair(Curve) { - var key_bytes = rand_bytes; - comptime var mask: u8 = 0xFF; - comptime { - while (mask >= Curve.order[0]) { - mask >>= 1; - } - } - key_bytes[0] &= mask; - key_bytes[Curve.point_len / 2 - 1] |= 0x01; - - return .{ - .secret_key = key_bytes, - .public_key = scalarmult(Curve, Curve.base_point, &key_bytes) catch unreachable, - }; - } - - fn jacobian_with_one_set(comptime Curve: type, comptime fields: [2][jacobian_len(Curve)]u32) Jacobian(Curve) { - comptime const plen = (Curve.P[0] + 63) >> 5; - return fields ++ [1][jacobian_len(Curve)]u32{ - [2]u32{ Curve.P[0], 1 } ++ ([1]u32{0} ** (plen - 2)), - }; - } - - fn encode_from_jacobian(comptime Curve: type, point: *[Curve.point_len]u8, P: Jacobian(Curve)) void { - var Q = P; - const T = comptime jacobian_with_one_set(Curve, [2][jacobian_len(Curve)]u32{ undefined, undefined }); - _ = run_code(Curve, &Q, T, &code.affine); - encode_jacobian_part(Curve, point[0 .. Curve.point_len / 2], Q[0]); - encode_jacobian_part(Curve, point[Curve.point_len / 2 ..], Q[1]); - } - - fn point_mul(comptime Curve: type, P: *Jacobian(Curve), x: []const u8) void { - var P2 = P.*; - point_double(Curve, &P2); - var P3 = P.*; - point_add(Curve, &P3, P2); - var Q = zero_jacobian(Curve); - var qz: u32 = 1; - var xlen = x.len; - var xidx: usize = 0; - while (xlen > 0) : ({ - xlen -= 1; - xidx += 1; - }) { - var k: u3 = 6; - while (true) : (k -= 2) { - point_double(Curve, &Q); - point_double(Curve, &Q); - var T = P.*; - var U = Q; - const bits = @as(u32, x[xidx] >> k) & 3; - const bnz = NEQ(bits, 0); - CCOPY(EQ(bits, 2), mem.asBytes(&T), mem.asBytes(&P2)); - CCOPY(EQ(bits, 3), mem.asBytes(&T), mem.asBytes(&P3)); - point_add(Curve, &U, T); - CCOPY(bnz & qz, mem.asBytes(&Q), mem.asBytes(&T)); - CCOPY(bnz & ~qz, mem.asBytes(&Q), mem.asBytes(&U)); - qz &= ~bnz; - - if (k == 0) - break; - } - } - P.* = Q; - } - - inline fn point_double(comptime Curve: type, P: *Jacobian(Curve)) void { - _ = run_code(Curve, P, P.*, &code.double); - } - inline fn point_add(comptime Curve: type, P1: *Jacobian(Curve), P2: Jacobian(Curve)) void { - _ = run_code(Curve, P1, P2, &code._add); - } - - fn decode_to_jacobian( - comptime Curve: type, - out: *Jacobian(Curve), - point: [Curve.point_len]u8, - ) u32 { - out.* = zero_jacobian(Curve); - var result = decode_mod(Curve, &out.*[0], point[0 .. Curve.point_len / 2].*); - result &= decode_mod(Curve, &out.*[1], point[Curve.point_len / 2 ..].*); - - const zlen = comptime ((Curve.P[0] + 63) >> 5); - comptime std.debug.assert(zlen == @typeInfo(@TypeOf(Curve.R2)).Array.len); - comptime std.debug.assert(zlen == @typeInfo(@TypeOf(Curve.B)).Array.len); - - const Q = comptime jacobian_with_one_set(Curve, [2][jacobian_len(Curve)]u32{ Curve.R2, Curve.B }); - result &= ~run_code(Curve, out, Q, &code.check); - return result; - } - - const code = struct { - const P1x = 0; - const P1y = 1; - const P1z = 2; - const P2x = 3; - const P2y = 4; - const P2z = 5; - const Px = 0; - const Py = 1; - const Pz = 2; - const t1 = 6; - const t2 = 7; - const t3 = 8; - const t4 = 9; - const t5 = 10; - const t6 = 11; - const t7 = 12; - const t8 = 3; - const t9 = 4; - const t10 = 5; - fn MSET(comptime d: u16, comptime a: u16) u16 { - return 0x0000 + (d << 8) + (a << 4); - } - fn MADD(comptime d: u16, comptime a: u16) u16 { - return 0x1000 + (d << 8) + (a << 4); - } - fn MSUB(comptime d: u16, comptime a: u16) u16 { - return 0x2000 + (d << 8) + (a << 4); - } - fn MMUL(comptime d: u16, comptime a: u16, comptime b: u16) u16 { - return 0x3000 + (d << 8) + (a << 4) + b; - } - fn MINV(comptime d: u16, comptime a: u16, comptime b: u16) u16 { - return 0x4000 + (d << 8) + (a << 4) + b; - } - fn MTZ(comptime d: u16) u16 { - return 0x5000 + (d << 8); - } - const ENDCODE = 0; - - const check = [_]u16{ - // Convert x and y to Montgomery representation. - MMUL(t1, P1x, P2x), - MMUL(t2, P1y, P2x), - MSET(P1x, t1), - MSET(P1y, t2), - // Compute x^3 in t1. - MMUL(t2, P1x, P1x), - MMUL(t1, P1x, t2), - // Subtract 3*x from t1. - MSUB(t1, P1x), - MSUB(t1, P1x), - MSUB(t1, P1x), - // Add b. - MADD(t1, P2y), - // Compute y^2 in t2. - MMUL(t2, P1y, P1y), - // Compare y^2 with x^3 - 3*x + b; they must match. - MSUB(t1, t2), - MTZ(t1), - // Set z to 1 (in Montgomery representation). - MMUL(P1z, P2x, P2z), - ENDCODE, - }; - const double = [_]u16{ - // Compute z^2 (in t1). - MMUL(t1, Pz, Pz), - // Compute x-z^2 (in t2) and then x+z^2 (in t1). - MSET(t2, Px), - MSUB(t2, t1), - MADD(t1, Px), - // Compute m = 3*(x+z^2)*(x-z^2) (in t1). - MMUL(t3, t1, t2), - MSET(t1, t3), - MADD(t1, t3), - MADD(t1, t3), - // Compute s = 4*x*y^2 (in t2) and 2*y^2 (in t3). - MMUL(t3, Py, Py), - MADD(t3, t3), - MMUL(t2, Px, t3), - MADD(t2, t2), - // Compute x' = m^2 - 2*s. - MMUL(Px, t1, t1), - MSUB(Px, t2), - MSUB(Px, t2), - // Compute z' = 2*y*z. - MMUL(t4, Py, Pz), - MSET(Pz, t4), - MADD(Pz, t4), - // Compute y' = m*(s - x') - 8*y^4. Note that we already have - // 2*y^2 in t3. - MSUB(t2, Px), - MMUL(Py, t1, t2), - MMUL(t4, t3, t3), - MSUB(Py, t4), - MSUB(Py, t4), - ENDCODE, - }; - const _add = [_]u16{ - // Compute u1 = x1*z2^2 (in t1) and s1 = y1*z2^3 (in t3). - MMUL(t3, P2z, P2z), - MMUL(t1, P1x, t3), - MMUL(t4, P2z, t3), - MMUL(t3, P1y, t4), - // Compute u2 = x2*z1^2 (in t2) and s2 = y2*z1^3 (in t4). - MMUL(t4, P1z, P1z), - MMUL(t2, P2x, t4), - MMUL(t5, P1z, t4), - MMUL(t4, P2y, t5), - //Compute h = u2 - u1 (in t2) and r = s2 - s1 (in t4). - MSUB(t2, t1), - MSUB(t4, t3), - // Report cases where r = 0 through the returned flag. - MTZ(t4), - // Compute u1*h^2 (in t6) and h^3 (in t5). - MMUL(t7, t2, t2), - MMUL(t6, t1, t7), - MMUL(t5, t7, t2), - // Compute x3 = r^2 - h^3 - 2*u1*h^2. - // t1 and t7 can be used as scratch registers. - MMUL(P1x, t4, t4), - MSUB(P1x, t5), - MSUB(P1x, t6), - MSUB(P1x, t6), - //Compute y3 = r*(u1*h^2 - x3) - s1*h^3. - MSUB(t6, P1x), - MMUL(P1y, t4, t6), - MMUL(t1, t5, t3), - MSUB(P1y, t1), - //Compute z3 = h*z1*z2. - MMUL(t1, P1z, P2z), - MMUL(P1z, t1, t2), - ENDCODE, - }; - const affine = [_]u16{ - // Save z*R in t1. - MSET(t1, P1z), - // Compute z^3 in t2. - MMUL(t2, P1z, P1z), - MMUL(t3, P1z, t2), - MMUL(t2, t3, P2z), - // Invert to (1/z^3) in t2. - MINV(t2, t3, t4), - // Compute y. - MSET(t3, P1y), - MMUL(P1y, t2, t3), - // Compute (1/z^2) in t3. - MMUL(t3, t2, t1), - // Compute x. - MSET(t2, P1x), - MMUL(P1x, t2, t3), - ENDCODE, - }; - }; - - fn decode_mod( - comptime Curve: type, - x: *[jacobian_len(Curve)]u32, - src: [Curve.point_len / 2]u8, - ) u32 { - const mlen = comptime ((Curve.P[0] + 31) >> 5); - const tlen = comptime std.math.max(mlen << 2, Curve.point_len / 2) + 4; - - var r: u32 = 0; - var pass: usize = 0; - while (pass < 2) : (pass += 1) { - var v: usize = 1; - var acc: u32 = 0; - var acc_len: u32 = 0; - - var u: usize = 0; - while (u < tlen) : (u += 1) { - const b = if (u < Curve.point_len / 2) - @as(u32, src[Curve.point_len / 2 - 1 - u]) - else - 0; - acc |= b << @truncate(u5, acc_len); - acc_len += 8; - if (acc_len >= 31) { - const xw = acc & 0x7FFFFFFF; - acc_len -= 31; - acc = b >> @truncate(u5, 8 - acc_len); - if (v <= mlen) { - if (pass != 0) { - x[v] = r & xw; - } else { - const cc = @bitCast(u32, CMP(xw, Curve.P[v])); - r = MUX(EQ(cc, 0), r, cc); - } - } else if (pass == 0) { - r = MUX(EQ(xw, 0), r, 1); - } - v += 1; - } - } - r >>= 1; - r |= (r << 1); - } - x[0] = Curve.P[0]; - return r & 1; - } - - fn run_code( - comptime Curve: type, - P1: *Jacobian(Curve), - P2: Jacobian(Curve), - comptime Code: []const u16, - ) u32 { - comptime const jaclen = jacobian_len(Curve); - - var t: [13][jaclen]u32 = undefined; - var result: u32 = 1; - - t[0..3].* = P1.*; - t[3..6].* = P2; - - comptime var u: usize = 0; - inline while (true) : (u += 1) { - comptime var op = Code[u]; - if (op == 0) - break; - comptime const d = (op >> 8) & 0x0F; - comptime const a = (op >> 4) & 0x0F; - comptime const b = op & 0x0F; - op >>= 12; - - switch (op) { - 0 => t[d] = t[a], - 1 => { - var ctl = add(jaclen, &t[d], t[a], 1); - ctl |= NOT(sub(jaclen, &t[d], Curve.P, 0)); - _ = sub(jaclen, &t[d], Curve.P, ctl); - }, - 2 => _ = add(jaclen, &t[d], Curve.P, sub(jaclen, &t[d], t[a], 1)), - 3 => montymul(Curve, &t[d], t[a], t[b], Curve.P, 1), - 4 => { - var tp: [Curve.point_len / 2]u8 = undefined; - encode_jacobian_part(Curve, &tp, Curve.P); - tp[Curve.point_len / 2 - 1] -= 2; - modpow(Curve, &t[d], tp, 1, &t[a], &t[b]); - }, - else => result &= ~iszero(jaclen, t[d]), - } - } - P1.* = t[0..3].*; - return result; - } - - inline fn MUL31(x: u32, y: u32) u64 { - return @as(u64, x) * @as(u64, y); - } - - inline fn MUL31_lo(x: u32, y: u32) u32 { - return (x *% y) & 0x7FFFFFFF; - } - - inline fn MUX(ctl: u32, x: u32, y: u32) u32 { - return y ^ (@bitCast(u32, -@bitCast(i32, ctl)) & (x ^ y)); - } - inline fn NOT(ctl: u32) u32 { - return ctl ^ 1; - } - inline fn NEQ(x: u32, y: u32) u32 { - const q = x ^ y; - return (q | @bitCast(u32, -@bitCast(i32, q))) >> 31; - } - inline fn EQ(x: u32, y: u32) u32 { - const q = x ^ y; - return NOT((q | @bitCast(u32, -@bitCast(i32, q))) >> 31); - } - inline fn CMP(x: u32, y: u32) i32 { - return @bitCast(i32, GT(x, y)) | -@bitCast(i32, GT(y, x)); - } - inline fn GT(x: u32, y: u32) u32 { - const z = y -% x; - return (z ^ ((x ^ y) & (x ^ z))) >> 31; - } - inline fn LT(x: u32, y: u32) u32 { - return GT(y, x); - } - inline fn GE(x: u32, y: u32) u32 { - return NOT(GT(y, x)); - } - - fn CCOPY(ctl: u32, dst: []u8, src: []const u8) void { - for (src) |s, i| { - dst[i] = @truncate(u8, MUX(ctl, s, dst[i])); - } - } - - // @TODO Remove lots of len and Curve parameters, just use the first byte calcualtions - // This will make all these functions shared for and reduce code bloat - - inline fn set_zero(comptime len: usize, out: *[len]u32, bit_len: u32) void { - out[0] = bit_len; - mem.set(u32, out[1..][0 .. (bit_len + 31) >> 5], 0); - } - - fn divrem(_hi: u32, _lo: u32, d: u32, r: *u32) u32 { - var hi = _hi; - var lo = _lo; - var q: u32 = 0; - const ch = EQ(hi, d); - hi = MUX(ch, 0, hi); - - var k: u5 = 31; - while (k > 0) : (k -= 1) { - const j = @truncate(u5, 32 - @as(u6, k)); - const w = (hi << j) | (lo >> k); - const ctl = GE(w, d) | (hi >> k); - const hi2 = (w -% d) >> j; - const lo2 = lo -% (d << k); - hi = MUX(ctl, hi2, hi); - lo = MUX(ctl, lo2, lo); - q |= ctl << k; - } - const cf = GE(lo, d) | hi; - q |= cf; - r.* = MUX(cf, lo -% d, lo); - return q; - } - - inline fn div(hi: u32, lo: u32, d: u32) u32 { - var r: u32 = undefined; - return divrem(hi, lo, d, &r); - } - - fn muladd_small(comptime len: usize, x: *[len]u32, z: u32, m: [len]u32) void { - var a0: u32 = undefined; - var a1: u32 = undefined; - var b0: u32 = undefined; - const mblr = @intCast(u5, m[0] & 31); - const mlen = (m[0] + 31) >> 5; - const hi = x[mlen]; - if (mblr == 0) { - a0 = x[mlen]; - mem.copyBackwards(u32, x[2..][0 .. mlen - 1], x[1..][0 .. mlen - 1]); - x[1] = z; - a1 = x[mlen]; - b0 = m[mlen]; - } else { - a0 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & 0x7FFFFFFF; - mem.copyBackwards(u32, x[2..][0 .. mlen - 1], x[1..][0 .. mlen - 1]); - x[1] = z; - a1 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & 0x7FFFFFFF; - b0 = ((m[mlen] << (31 - mblr)) | (m[mlen - 1] >> mblr)) & 0x7FFFFFFF; - } - - const g = div(a0 >> 1, a1 | (a0 << 31), b0); - const q = MUX(EQ(a0, b0), 0x7FFFFFFF, MUX(EQ(g, 0), 0, g -% 1)); - - var cc: u32 = 0; - var tb: u32 = 1; - var u: usize = 1; - while (u <= mlen) : (u += 1) { - const mw = m[u]; - const zl = MUL31(mw, q) + cc; - cc = @truncate(u32, zl >> 31); - const zw = @truncate(u32, zl) & 0x7FFFFFFF; - const xw = x[u]; - var nxw = xw -% zw; - cc += nxw >> 31; - nxw &= 0x7FFFFFFF; - x[u] = nxw; - tb = MUX(EQ(nxw, mw), tb, GT(nxw, mw)); - } - - const over = GT(cc, hi); - const under = ~over & (tb | LT(cc, hi)); - _ = add(len, x, m, over); - _ = sub(len, x, m, under); - } - - fn to_monty(comptime len: usize, x: *[len]u32, m: [len]u32) void { - const mlen = (m[0] + 31) >> 5; - var k = mlen; - while (k > 0) : (k -= 1) { - muladd_small(len, x, 0, m); - } - } - - fn modpow( - comptime Curve: type, - x: *[jacobian_len(Curve)]u32, - e: [Curve.point_len / 2]u8, - m0i: u32, - t1: *[jacobian_len(Curve)]u32, - t2: *[jacobian_len(Curve)]u32, - ) void { - comptime const jaclen = jacobian_len(Curve); - t1.* = x.*; - to_monty(jaclen, t1, Curve.P); - set_zero(jaclen, x, Curve.P[0]); - x[1] = 1; - comptime const bitlen = (Curve.point_len / 2) << 3; - var k: usize = 0; - while (k < bitlen) : (k += 1) { - const ctl = (e[Curve.point_len / 2 - 1 - (k >> 3)] >> (@truncate(u3, k & 7))) & 1; - montymul(Curve, t2, x.*, t1.*, Curve.P, m0i); - CCOPY(ctl, mem.asBytes(x), mem.asBytes(t2)); - montymul(Curve, t2, t1.*, t1.*, Curve.P, m0i); - t1.* = t2.*; - } - } - - fn encode_jacobian_part(comptime Curve: type, dst: *[Curve.point_len / 2]u8, x: [jacobian_len(Curve)]u32) void { - const xlen = (x[0] + 31) >> 5; - - var buf = @ptrToInt(dst) + Curve.point_len / 2; - var len: usize = Curve.point_len / 2; - var k: usize = 1; - var acc: u32 = 0; - var acc_len: u5 = 0; - while (len != 0) { - const w = if (k <= xlen) x[k] else 0; - k += 1; - if (acc_len == 0) { - acc = w; - acc_len = 31; - } else { - const z = acc | (w << acc_len); - acc_len -= 1; - acc = w >> (31 - acc_len); - if (len >= 4) { - buf -= 4; - len -= 4; - mem.writeIntBig(u32, @intToPtr([*]u8, buf)[0..4], z); - } else { - switch (len) { - 3 => { - @intToPtr(*u8, buf - 3).* = @truncate(u8, z >> 16); - @intToPtr(*u8, buf - 2).* = @truncate(u8, z >> 8); - }, - 2 => @intToPtr(*u8, buf - 2).* = @truncate(u8, z >> 8), - 1 => {}, - else => unreachable, - } - @intToPtr(*u8, buf - 1).* = @truncate(u8, z); - return; - } - } - } - } - - fn montymul( - comptime Curve: type, - out: *[jacobian_len(Curve)]u32, - x: [jacobian_len(Curve)]u32, - y: [jacobian_len(Curve)]u32, - m: [jacobian_len(Curve)]u32, - m0i: u32, - ) void { - comptime const jaclen = jacobian_len(Curve); - const len = (m[0] + 31) >> 5; - const len4 = len & ~@as(usize, 3); - set_zero(jaclen, out, m[0]); - var dh: u32 = 0; - var u: usize = 0; - while (u < len) : (u += 1) { - const xu = x[u + 1]; - const f = MUL31_lo(out[1] + MUL31_lo(x[u + 1], y[1]), m0i); - - var r: u64 = 0; - var v: usize = 0; - while (v < len4) : (v += 4) { - comptime var j = 1; - inline while (j <= 4) : (j += 1) { - const z = out[v + j] +% MUL31(xu, y[v + j]) +% MUL31(f, m[v + j]) +% r; - r = z >> 31; - out[v + j - 1] = @truncate(u32, z) & 0x7FFFFFFF; - } - } - while (v < len) : (v += 1) { - const z = out[v + 1] +% MUL31(xu, y[v + 1]) +% MUL31(f, m[v + 1]) +% r; - r = z >> 31; - out[v] = @truncate(u32, z) & 0x7FFFFFFF; - } - dh += @truncate(u32, r); - out[len] = dh & 0x7FFFFFFF; - dh >>= 31; - } - out[0] = m[0]; - const ctl = NEQ(dh, 0) | NOT(sub(jaclen, out, m, 0)); - _ = sub(jaclen, out, m, ctl); - } - - fn add(comptime len: usize, a: *[len]u32, b: [len]u32, ctl: u32) u32 { - var u: usize = 1; - var cc: u32 = 0; - while (u < len) : (u += 1) { - const aw = a[u]; - const bw = b[u]; - const naw = aw +% bw +% cc; - cc = naw >> 31; - a[u] = MUX(ctl, naw & 0x7FFFFFFF, aw); - } - return cc; - } - - fn sub(comptime len: usize, a: *[len]u32, b: [len]u32, ctl: u32) u32 { - var cc: u32 = 0; - const m = (a[0] + 63) >> 5; - var u: usize = 1; - while (u < m) : (u += 1) { - const aw = a[u]; - const bw = b[u]; - const naw = aw -% bw -% cc; - cc = naw >> 31; - a[u] = MUX(ctl, naw & 0x7FFFFFFF, aw); - } - return cc; - } - - fn iszero(comptime len: usize, arr: [len]u32) u32 { - var z: u32 = 0; - var u: usize = len - 1; - while (u > 0) : (u -= 1) { - z |= arr[u]; - } - return ~(z | @bitCast(u32, -@bitCast(i32, z))) >> 31; - } -}; - -test "elliptic curve functions with secp384r1 curve" { - { - // Decode to Jacobian then encode again with no operations - var P: ecc.Jacobian(ecc.SECP384R1) = undefined; - var res: u32 = ecc.decode_to_jacobian(ecc.SECP384R1, &P, ecc.SECP384R1.base_point); - var out: [96]u8 = undefined; - ecc.encode_from_jacobian(ecc.SECP384R1, &out, P); - std.testing.expectEqual(ecc.SECP384R1.base_point, out); - - // Multiply by one, check that the result is still the base point - mem.set(u8, &out, 0); - ecc.point_mul(ecc.SECP384R1, &P, &[1]u8{1}); - ecc.encode_from_jacobian(ecc.SECP384R1, &out, P); - std.testing.expectEqual(ecc.SECP384R1.base_point, out); - } - - { - // @TODO Remove this once std.crypto.rand works in .evented mode - var rand = blk: { - var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined; - try std.os.getrandom(&seed); - break :blk &std.rand.DefaultCsprng.init(seed).random; - }; - - // Derive a shared secret from a Diffie-Hellman key exchange - var seed: [48]u8 = undefined; - rand.bytes(&seed); - const kp1 = ecc.make_key_pair(ecc.SECP384R1, seed); - rand.bytes(&seed); - const kp2 = ecc.make_key_pair(ecc.SECP384R1, seed); - - const shared1 = try ecc.scalarmult(ecc.SECP384R1, kp1.public_key, &kp2.secret_key); - const shared2 = try ecc.scalarmult(ecc.SECP384R1, kp2.public_key, &kp1.secret_key); - std.testing.expectEqual(shared1, shared2); - } - - // @TODO Add tests with known points. -} diff --git a/libs/iguanatls/src/main.zig b/libs/iguanatls/src/main.zig deleted file mode 100644 index d57bfbb2acd85dba8376e3654d9f2fb5c89e0884..0000000000000000000000000000000000000000 --- a/libs/iguanatls/src/main.zig +++ /dev/null @@ -1,1843 +0,0 @@ -const std = @import("std"); -const mem = std.mem; -const Allocator = mem.Allocator; -const Sha384 = std.crypto.hash.sha2.Sha384; -const Sha512 = std.crypto.hash.sha2.Sha512; -const Sha256 = std.crypto.hash.sha2.Sha256; -const Hmac256 = std.crypto.auth.hmac.sha2.HmacSha256; - -pub const asn1 = @import("asn1.zig"); -pub const x509 = @import("x509.zig"); -pub const crypto = @import("crypto.zig"); - -const ciphers = @import("ciphersuites.zig"); -pub const ciphersuites = ciphers.suites; - -comptime { - std.testing.refAllDecls(x509); - std.testing.refAllDecls(asn1); - std.testing.refAllDecls(crypto); -} - -fn handshake_record_length(reader: anytype) !usize { - return try record_length(0x16, reader); -} - -pub const RecordTagLength = struct { - tag: u8, - length: u16, -}; -pub fn record_tag_length(reader: anytype) !RecordTagLength { - const record_tag = try reader.readByte(); - - var record_header: [4]u8 = undefined; - try reader.readNoEof(&record_header); - - if (!mem.eql(u8, record_header[0..2], "\x03\x03") and !mem.eql(u8, record_header[0..2], "\x03\x01")) - return error.ServerInvalidVersion; - - const len = mem.readIntSliceBig(u16, record_header[2..4]); - return RecordTagLength{ - .tag = record_tag, - .length = len, - }; -} - -pub fn record_length(t: u8, reader: anytype) !usize { - try check_record_type(t, reader); - var record_header: [4]u8 = undefined; - try reader.readNoEof(&record_header); - if (!mem.eql(u8, record_header[0..2], "\x03\x03") and !mem.eql(u8, record_header[0..2], "\x03\x01")) - return error.ServerInvalidVersion; - return mem.readIntSliceBig(u16, record_header[2..4]); -} - -pub const ServerAlert = error{ - AlertCloseNotify, - AlertUnexpectedMessage, - AlertBadRecordMAC, - AlertDecryptionFailed, - AlertRecordOverflow, - AlertDecompressionFailure, - AlertHandshakeFailure, - AlertNoCertificate, - AlertBadCertificate, - AlertUnsupportedCertificate, - AlertCertificateRevoked, - AlertCertificateExpired, - AlertCertificateUnknown, - AlertIllegalParameter, - AlertUnknownCA, - AlertAccessDenied, - AlertDecodeError, - AlertDecryptError, - AlertExportRestriction, - AlertProtocolVersion, - AlertInsufficientSecurity, - AlertInternalError, - AlertUserCanceled, - AlertNoRenegotiation, - AlertUnsupportedExtension, -}; - -fn check_record_type( - expected: u8, - reader: anytype, -) (@TypeOf(reader).Error || ServerAlert || error{ ServerMalformedResponse, EndOfStream })!void { - const record_type = try reader.readByte(); - // Alert - if (record_type == 0x15) { - // Skip SSL version, length of record - try reader.skipBytes(4, .{}); - - const severity = try reader.readByte(); - const err_num = try reader.readByte(); - return alert_byte_to_error(err_num); - } - if (record_type != expected) - return error.ServerMalformedResponse; -} - -pub fn alert_byte_to_error(b: u8) (ServerAlert || error{ServerMalformedResponse}) { - return switch (b) { - 0 => error.AlertCloseNotify, - 10 => error.AlertUnexpectedMessage, - 20 => error.AlertBadRecordMAC, - 21 => error.AlertDecryptionFailed, - 22 => error.AlertRecordOverflow, - 30 => error.AlertDecompressionFailure, - 40 => error.AlertHandshakeFailure, - 41 => error.AlertNoCertificate, - 42 => error.AlertBadCertificate, - 43 => error.AlertUnsupportedCertificate, - 44 => error.AlertCertificateRevoked, - 45 => error.AlertCertificateExpired, - 46 => error.AlertCertificateUnknown, - 47 => error.AlertIllegalParameter, - 48 => error.AlertUnknownCA, - 49 => error.AlertAccessDenied, - 50 => error.AlertDecodeError, - 51 => error.AlertDecryptError, - 60 => error.AlertExportRestriction, - 70 => error.AlertProtocolVersion, - 71 => error.AlertInsufficientSecurity, - 80 => error.AlertInternalError, - 90 => error.AlertUserCanceled, - 100 => error.AlertNoRenegotiation, - 110 => error.AlertUnsupportedExtension, - else => error.ServerMalformedResponse, - }; -} - -fn Sha256Reader(comptime Reader: anytype) type { - const State = struct { - sha256: *Sha256, - reader: Reader, - }; - const S = struct { - pub fn read(state: State, buffer: []u8) Reader.Error!usize { - const amt = try state.reader.read(buffer); - if (amt != 0) { - state.sha256.update(buffer[0..amt]); - } - return amt; - } - }; - return std.io.Reader(State, Reader.Error, S.read); -} - -fn sha256_reader(sha256: *Sha256, reader: anytype) Sha256Reader(@TypeOf(reader)) { - return .{ .context = .{ .sha256 = sha256, .reader = reader } }; -} - -fn Sha256Writer(comptime Writer: anytype) type { - const State = struct { - sha256: *Sha256, - writer: Writer, - }; - const S = struct { - pub fn write(state: State, buffer: []const u8) Writer.Error!usize { - const amt = try state.writer.write(buffer); - if (amt != 0) { - state.sha256.update(buffer[0..amt]); - } - return amt; - } - }; - return std.io.Writer(State, Writer.Error, S.write); -} - -fn sha256_writer(sha256: *Sha256, writer: anytype) Sha256Writer(@TypeOf(writer)) { - return .{ .context = .{ .sha256 = sha256, .writer = writer } }; -} - -fn CertificateReaderState(comptime Reader: type) type { - return struct { - reader: Reader, - length: usize, - idx: usize = 0, - }; -} - -fn CertificateReader(comptime Reader: type) type { - const S = struct { - pub fn read(state: *CertificateReaderState(Reader), buffer: []u8) Reader.Error!usize { - const out_bytes = std.math.min(buffer.len, state.length - state.idx); - const res = try state.reader.readAll(buffer[0..out_bytes]); - state.idx += res; - return res; - } - }; - - return std.io.Reader(*CertificateReaderState(Reader), Reader.Error, S.read); -} - -pub const CertificateVerifier = union(enum) { - none, - function: anytype, - default, -}; - -pub fn CertificateVerifierReader(comptime Reader: type) type { - return CertificateReader(Sha256Reader(Reader)); -} - -pub fn ClientConnectError(comptime verifier: CertificateVerifier, comptime Reader: type, comptime Writer: type) type { - const Additional = error{ - ServerInvalidVersion, - ServerMalformedResponse, - EndOfStream, - ServerInvalidCipherSuite, - ServerInvalidCompressionMethod, - ServerInvalidRenegotiationData, - ServerInvalidECPointCompression, - ServerInvalidProtocol, - ServerInvalidExtension, - ServerInvalidCurve, - ServerInvalidSignature, - ServerInvalidSignatureAlgorithm, - ServerAuthenticationFailed, - ServerInvalidVerifyData, - PreMasterGenerationFailed, - OutOfMemory, - }; - const err_msg = "Certificate verifier function cannot be generic, use CertificateVerifierReader to get the reader argument type"; - return Reader.Error || Writer.Error || ServerAlert || Additional || switch (verifier) { - .none => error{}, - .function => |f| @typeInfo(@typeInfo(@TypeOf(f)).Fn.return_type orelse - @compileError(err_msg)).ErrorUnion.error_set || error{CertificateVerificationFailed}, - .default => error{CertificateVerificationFailed}, - }; -} - -// See http://howardhinnant.github.io/date_algorithms.html -// Timestamp in seconds, only supports A.D. dates -fn unix_timestamp_from_civil_date(year: u16, month: u8, day: u8) i64 { - var y: i64 = year; - if (month <= 2) y -= 1; - const era = @divTrunc(y, 400); - const yoe = y - era * 400; // [0, 399] - const doy = @divTrunc((153 * (month + (if (month > 2) @as(i64, -3) else 9)) + 2), 5) + day - 1; // [0, 365] - const doe = yoe * 365 + @divTrunc(yoe, 4) - @divTrunc(yoe, 100) + doy; // [0, 146096] - return (era * 146097 + doe - 719468) * 86400; -} - -fn read_der_utc_timestamp(reader: anytype) !i64 { - var buf: [17]u8 = undefined; - - const tag = try reader.readByte(); - if (tag != 0x17) - return error.CertificateVerificationFailed; - const len = try asn1.der.parse_length(reader); - if (len > 17) - return error.CertificateVerificationFailed; - - try reader.readNoEof(buf[0..len]); - const year = std.fmt.parseUnsigned(u16, buf[0..2], 10) catch - return error.CertificateVerificationFailed; - const month = std.fmt.parseUnsigned(u8, buf[2..4], 10) catch - return error.CertificateVerificationFailed; - const day = std.fmt.parseUnsigned(u8, buf[4..6], 10) catch - return error.CertificateVerificationFailed; - - var time = unix_timestamp_from_civil_date(2000 + year, month, day); - time += (std.fmt.parseUnsigned(i64, buf[6..8], 10) catch - return error.CertificateVerificationFailed) * 3600; - time += (std.fmt.parseUnsigned(i64, buf[8..10], 10) catch - return error.CertificateVerificationFailed) * 60; - - if (buf[len - 1] == 'Z') { - if (len == 13) { - time += std.fmt.parseUnsigned(u8, buf[10..12], 10) catch - return error.CertificateVerificationFailed; - } else if (len != 11) { - return error.CertificateVerificationFailed; - } - } else { - if (len == 15) { - if (buf[10] != '+' and buf[10] != '-') - return error.CertificateVerificationFailed; - - var additional = (std.fmt.parseUnsigned(i64, buf[11..13], 10) catch - return error.CertificateVerificationFailed) * 3600; - additional += (std.fmt.parseUnsigned(i64, buf[13..15], 10) catch - return error.CertificateVerificationFailed) * 60; - - time += if (buf[10] == '+') -additional else additional; - } else if (len == 17) { - if (buf[12] != '+' and buf[12] != '-') - return error.CertificateVerificationFailed; - time += std.fmt.parseUnsigned(u8, buf[10..12], 10) catch - return error.CertificateVerificationFailed; - - var additional = (std.fmt.parseUnsigned(i64, buf[13..15], 10) catch - return error.CertificateVerificationFailed) * 3600; - additional += (std.fmt.parseUnsigned(i64, buf[15..17], 10) catch - return error.CertificateVerificationFailed) * 60; - - time += if (buf[12] == '+') -additional else additional; - } else return error.CertificateVerificationFailed; - } - return time; -} - -fn check_cert_timestamp(time: i64, tag_byte: u8, length: usize, reader: anytype) !void { - if (time < (try read_der_utc_timestamp(reader))) - return error.CertificateVerificationFailed; - if (time > (try read_der_utc_timestamp(reader))) - return error.CertificateVerificationFailed; -} - -fn add_dn_field(state: *VerifierCaptureState, tag: u8, length: usize, reader: anytype) !void { - const seq_tag = try reader.readByte(); - if (seq_tag != 0x30) - return error.CertificateVerificationFailed; - const seq_length = try asn1.der.parse_length(reader); - - const oid_tag = try reader.readByte(); - if (oid_tag != 0x06) - return error.CertificateVerificationFailed; - - const oid_length = try asn1.der.parse_length(reader); - if (oid_length == 3 and (try reader.isBytes("\x55\x04\x03"))) { - // Common name - const common_name_tag = try reader.readByte(); - if (common_name_tag != 0x04 and common_name_tag != 0x0c and common_name_tag != 0x13 and common_name_tag != 0x16) - return error.CertificateVerificationFailed; - const common_name_len = try asn1.der.parse_length(reader); - state.list.items[state.list.items.len - 1].common_name = state.fbs.buffer[state.fbs.pos .. state.fbs.pos + common_name_len]; - } -} - -fn add_cert_subject_dn(state: *VerifierCaptureState, tag: u8, length: usize, reader: anytype) !void { - state.list.items[state.list.items.len - 1].dn = state.fbs.buffer[state.fbs.pos .. state.fbs.pos + length]; - const schema = .{ - .sequence_of, - .{ - .capture, 0, .set, - }, - }; - const captures = .{ - state, add_dn_field, - }; - try asn1.der.parse_schema_tag_len(tag, length, schema, captures, reader); -} - -fn add_cert_public_key(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void { - state.list.items[state.list.items.len - 1].public_key = x509.parse_public_key( - state.allocator, - reader, - ) catch |err| switch (err) { - error.MalformedDER => return error.CertificateVerificationFailed, - else => |e| return e, - }; -} - -fn add_server_cert(state: *VerifierCaptureState, tag_byte: u8, length: usize, reader: anytype) !void { - const is_ca = state.list.items.len != 0; - - const encoded_length = asn1.der.encode_length(length).slice(); - // This is not errdefered since default_cert_verifier call takes care of cleaning up all the certificate data. - // Same for the signature.data - const cert_bytes = try state.allocator.alloc(u8, length + 1 + encoded_length.len); - cert_bytes[0] = tag_byte; - mem.copy(u8, cert_bytes[1 .. 1 + encoded_length.len], encoded_length); - - try reader.readNoEof(cert_bytes[1 + encoded_length.len ..]); - (try state.list.addOne(state.allocator)).* = .{ - .is_ca = is_ca, - .bytes = cert_bytes, - .dn = undefined, - .common_name = &[0]u8{}, - .public_key = x509.PublicKey.empty, - .signature = asn1.BitString{ .data = &[0]u8{}, .bit_len = 0 }, - .signature_algorithm = undefined, - }; - - const schema = .{ - .sequence, - .{ - .{ .context_specific, 0 }, // version - .{.int}, // serialNumber - .{.sequence}, // signature - .{.sequence}, // issuer - .{ .capture, 0, .sequence }, // validity - .{ .capture, 1, .sequence }, // subject - .{ .capture, 2, .sequence }, // subjectPublicKeyInfo - .{ .optional, .context_specific, 1 }, // issuerUniqueID - .{ .optional, .context_specific, 2 }, // subjectUniqueID - .{ .optional, .context_specific, 3 }, // extensions - }, - }; - - const captures = .{ - std.time.timestamp(), check_cert_timestamp, - state, add_cert_subject_dn, - state, add_cert_public_key, - }; - - var fbs = std.io.fixedBufferStream(@as([]const u8, cert_bytes[1 + encoded_length.len ..])); - state.fbs = &fbs; - - asn1.der.parse_schema_tag_len(tag_byte, length, schema, captures, fbs.reader()) catch |err| switch (err) { - error.InvalidLength, - error.InvalidTag, - error.InvalidContainerLength, - error.DoesNotMatchSchema, - => return error.CertificateVerificationFailed, - else => |e| return e, - }; -} - -fn set_signature_algorithm(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void { - const oid_tag = try reader.readByte(); - if (oid_tag != 0x06) - return error.CertificateVerificationFailed; - - const oid_length = try asn1.der.parse_length(reader); - if (oid_length == 9) { - var oid_bytes: [9]u8 = undefined; - try reader.readNoEof(&oid_bytes); - - const cert = &state.list.items[state.list.items.len - 1]; - if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 })) { - cert.signature_algorithm = .rsa; - } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 })) { - cert.signature_algorithm = .rsa_md5; - } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 })) { - cert.signature_algorithm = .rsa_sha1; - } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B })) { - cert.signature_algorithm = .rsa_sha256; - } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C })) { - cert.signature_algorithm = .rsa_sha384; - } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D })) { - cert.signature_algorithm = .rsa_sha512; - } else { - return error.CertificateVerificationFailed; - } - return; - } else if (oid_length == 10) { - // @TODO - // ECDSA + algorithms - } - - return error.CertificateVerificationFailed; -} - -fn set_signature_value(state: *VerifierCaptureState, tag: u8, length: usize, reader: anytype) !void { - const unused_bits = try reader.readByte(); - const bit_count = (length - 1) * 8 - unused_bits; - const signature_bytes = try state.allocator.alloc(u8, length - 1); - errdefer state.allocator.free(signature_bytes); - try reader.readNoEof(signature_bytes); - state.list.items[state.list.items.len - 1].signature = .{ - .data = signature_bytes, - .bit_len = bit_count, - }; -} - -fn verify_signature( - allocator: *Allocator, - signature_algorithm: SignatureAlgorithm, - signature: asn1.BitString, - hash: []const u8, - public_key: x509.PublicKey, -) !bool { - // @TODO ECDSA algorithms - if (public_key != .rsa) return false; - const prefix: []const u8 = switch (signature_algorithm) { - // Deprecated hash algos - .rsa_md5, .rsa_sha1 => return false, - // @TODO How does this one work? - .rsa => return false, - .rsa_sha256 => &[_]u8{ - 0x30, 0x31, 0x30, 0x0d, 0x06, - 0x09, 0x60, 0x86, 0x48, 0x01, - 0x65, 0x03, 0x04, 0x02, 0x01, - 0x05, 0x00, 0x04, 0x20, - }, - .rsa_sha384 => &[_]u8{ - 0x30, 0x41, 0x30, 0x0d, 0x06, - 0x09, 0x60, 0x86, 0x48, 0x01, - 0x65, 0x03, 0x04, 0x02, 0x02, - 0x05, 0x00, 0x04, 0x30, - }, - .rsa_sha512 => &[_]u8{ - 0x30, 0x51, 0x30, 0x0d, 0x06, - 0x09, 0x60, 0x86, 0x48, 0x01, - 0x65, 0x03, 0x04, 0x02, 0x03, - 0x05, 0x00, 0x04, 0x40, - }, - }; - - // RSA hash verification with PKCS 1 V1_5 padding - const modulus = std.math.big.int.Const{ .limbs = public_key.rsa.modulus, .positive = true }; - const exponent = std.math.big.int.Const{ .limbs = public_key.rsa.exponent, .positive = true }; - if (modulus.bitCountAbs() != signature.bit_len) - return false; - - // encrypt the signature using the RSA key - // @TODO better algorithm, this is probably slow as hell - var encrypted_signature = try std.math.big.int.Managed.initSet(allocator, @as(usize, 1)); - defer encrypted_signature.deinit(); - - { - var curr_exponent = try exponent.toManaged(allocator); - defer curr_exponent.deinit(); - - const curr_base_limbs = try allocator.alloc( - usize, - std.math.divCeil(usize, signature.data.len, @sizeOf(usize)) catch unreachable, - ); - const curr_base_limb_bytes = @ptrCast([*]u8, curr_base_limbs)[0..signature.data.len]; - mem.copy(u8, curr_base_limb_bytes, signature.data); - mem.reverse(u8, curr_base_limb_bytes); - var curr_base = (std.math.big.int.Mutable{ - .limbs = curr_base_limbs, - .positive = true, - .len = curr_base_limbs.len, - }).toManaged(allocator); - defer curr_base.deinit(); - - // encrypted = signature ^ key.exponent MOD key.modulus - while (curr_exponent.toConst().orderAgainstScalar(0) == .gt) { - if (curr_exponent.isOdd()) { - try encrypted_signature.ensureMulCapacity(encrypted_signature.toConst(), curr_base.toConst()); - try encrypted_signature.mul(encrypted_signature.toConst(), curr_base.toConst()); - try llmod(&encrypted_signature, modulus); - } - try curr_base.sqr(curr_base.toConst()); - try llmod(&curr_base, modulus); - try curr_exponent.shiftRight(curr_exponent, 1); - } - } - // EMSA-PKCS1-V1_5-ENCODE - if (encrypted_signature.limbs.len * @sizeOf(usize) < signature.data.len) - return false; - - const enc_buf = @ptrCast([*]u8, encrypted_signature.limbs.ptr)[0..signature.data.len]; - mem.reverse(u8, enc_buf); - - if (enc_buf[0] != 0x00 or enc_buf[1] != 0x01) - return false; - if (!mem.endsWith(u8, enc_buf, hash)) - return false; - if (!mem.endsWith(u8, enc_buf[0 .. enc_buf.len - hash.len], prefix)) - return false; - if (enc_buf[enc_buf.len - hash.len - prefix.len - 1] != 0x00) - return false; - for (enc_buf[2 .. enc_buf.len - hash.len - prefix.len - 1]) |c| { - if (c != 0xff) return false; - } - - return true; -} - -fn certificate_verify_signature( - allocator: *Allocator, - signature_algorithm: SignatureAlgorithm, - signature: asn1.BitString, - bytes: []const u8, - public_key: x509.PublicKey, -) !bool { - // @TODO ECDSA algorithms - if (public_key != .rsa) return false; - - var hash_buf: [64]u8 = undefined; - var hash: []u8 = undefined; - - switch (signature_algorithm) { - // Deprecated hash algos - .rsa_md5, .rsa_sha1 => return false, - // @TODO How does this one work? - .rsa => return false, - - .rsa_sha256 => { - Sha256.hash(bytes, hash_buf[0..32], .{}); - hash = hash_buf[0..32]; - }, - .rsa_sha384 => { - Sha384.hash(bytes, hash_buf[0..48], .{}); - hash = hash_buf[0..48]; - }, - .rsa_sha512 => { - Sha512.hash(bytes, hash_buf[0..64], .{}); - hash = &hash_buf; - }, - } - return try verify_signature(allocator, signature_algorithm, signature, hash, public_key); -} - -// res = res mod N -fn llmod(res: *std.math.big.int.Managed, n: std.math.big.int.Const) !void { - var temp = try std.math.big.int.Managed.init(res.allocator); - defer temp.deinit(); - try temp.divTrunc(res, res.toConst(), n); -} - -const SignatureAlgorithm = enum { - rsa, - rsa_md5, - rsa_sha1, - rsa_sha256, - rsa_sha384, - rsa_sha512, - // @TODO ECDSA versions -}; - -const ServerCertificate = struct { - bytes: []const u8, - dn: []const u8, - common_name: []const u8, - public_key: x509.PublicKey, - signature: asn1.BitString, - signature_algorithm: SignatureAlgorithm, - is_ca: bool, -}; - -const VerifierCaptureState = struct { - list: std.ArrayListUnmanaged(ServerCertificate), - allocator: *Allocator, - // Used in `add_server_cert` to avoid an extra allocation - fbs: *std.io.FixedBufferStream([]const u8), -}; - -// @TODO Move out of here -const ReverseSplitIterator = struct { - buffer: []const u8, - index: ?usize, - delimiter: []const u8, - - pub fn next(self: *ReverseSplitIterator) ?[]const u8 { - const end = self.index orelse return null; - const start = if (mem.lastIndexOfLinear(u8, self.buffer[0..end], self.delimiter)) |delim_start| blk: { - self.index = delim_start; - break :blk delim_start + self.delimiter.len; - } else blk: { - self.index = null; - break :blk 0; - }; - return self.buffer[start..end]; - } -}; - -fn reverse_split(buffer: []const u8, delimiter: []const u8) ReverseSplitIterator { - std.debug.assert(delimiter.len != 0); - return .{ - .index = buffer.len, - .buffer = buffer, - .delimiter = delimiter, - }; -} - -pub fn default_cert_verifier( - allocator: *mem.Allocator, - reader: anytype, - certs_bytes: usize, - trusted_certificates: []const x509.TrustAnchor, - hostname: []const u8, -) !x509.PublicKey { - var capture_state = VerifierCaptureState{ - .list = try std.ArrayListUnmanaged(ServerCertificate).initCapacity(allocator, 3), - .allocator = allocator, - .fbs = undefined, - }; - defer { - for (capture_state.list.items) |cert| { - cert.public_key.deinit(allocator); - allocator.free(cert.bytes); - allocator.free(cert.signature.data); - } - capture_state.list.deinit(allocator); - } - - const schema = .{ - .sequence, .{ - // tbsCertificate - .{ .capture, 0, .sequence }, - // signatureAlgorithm - .{ .capture, 1, .sequence }, - // signatureValue - .{ .capture, 2, .bit_string }, - }, - }; - const captures = .{ - &capture_state, add_server_cert, - &capture_state, set_signature_algorithm, - &capture_state, set_signature_value, - }; - - var bytes_read: u24 = 0; - while (bytes_read < certs_bytes) { - const cert_length = try reader.readIntBig(u24); - - asn1.der.parse_schema(schema, captures, reader) catch |err| switch (err) { - error.InvalidLength, - error.InvalidTag, - error.InvalidContainerLength, - error.DoesNotMatchSchema, - => return error.CertificateVerificationFailed, - else => |e| return e, - }; - - bytes_read += 3 + cert_length; - } - if (bytes_read != certs_bytes) - return error.CertificateVerificationFailed; - - const chain = capture_state.list.items; - if (chain.len == 0) return error.CertificateVerificationFailed; - // Check if the hostname matches the leaf certificate's common name - { - var common_name_split = reverse_split(chain[0].common_name, "."); - var hostname_split = reverse_split(hostname, "."); - while (true) { - const cn_part = common_name_split.next(); - const hn_part = hostname_split.next(); - - if (cn_part) |cnp| { - if (hn_part == null and common_name_split.index == null and mem.eql(u8, cnp, "www")) - break - else if (hn_part) |hnp| { - if (mem.eql(u8, cnp, "*")) - continue; - if (!mem.eql(u8, cnp, hnp)) - return error.CertificateVerificationFailed; - } - } else if (hn_part != null) - return error.CertificateVerificationFailed - else - break; - } - } - - var i: usize = 0; - while (i < chain.len - 1) : (i += 1) { - if (!try certificate_verify_signature( - allocator, - chain[i].signature_algorithm, - chain[i].signature, - chain[i].bytes, - chain[i + 1].public_key, - )) { - return error.CertificateVerificationFailed; - } - } - - for (chain) |cert| { - for (trusted_certificates) |trusted| { - // Try to find an exact match to a trusted certificate - if (cert.is_ca == trusted.is_ca and mem.eql(u8, cert.dn, trusted.dn) and - cert.public_key.eql(trusted.public_key)) - { - const key = chain[0].public_key; - chain[0].public_key = x509.PublicKey.empty; - return key; - } - - if (!trusted.is_ca) - continue; - - if (try certificate_verify_signature( - allocator, - cert.signature_algorithm, - cert.signature, - cert.bytes, - trusted.public_key, - )) { - const key = chain[0].public_key; - chain[0].public_key = x509.PublicKey.empty; - return key; - } - } - } - return error.CertificateVerificationFailed; -} - -pub fn extract_cert_public_key(allocator: *Allocator, reader: anytype, length: usize) !x509.PublicKey { - const CaptureState = struct { - pub_key: x509.PublicKey, - allocator: *Allocator, - }; - var capture_state = CaptureState{ - .pub_key = undefined, - .allocator = allocator, - }; - - var pub_key: x509.PublicKey = undefined; - const schema = .{ - .sequence, .{ - // tbsCertificate - .{ - .sequence, - .{ - .{ .context_specific, 0 }, // version - .{.int}, // serialNumber - .{.sequence}, // signature - .{.sequence}, // issuer - .{.sequence}, // validity - .{.sequence}, // subject - .{ .capture, 0, .sequence }, // subjectPublicKeyInfo - .{ .optional, .context_specific, 1 }, // issuerUniqueID - .{ .optional, .context_specific, 2 }, // subjectUniqueID - .{ .optional, .context_specific, 3 }, // extensions - }, - }, - // signatureAlgorithm - .{.sequence}, - // signatureValue - .{.bit_string}, - }, - }; - const captures = .{ - &capture_state, struct { - fn f(state: *CaptureState, tag: u8, _: usize, subreader: anytype) !void { - state.pub_key = x509.parse_public_key(state.allocator, subreader) catch |err| switch (err) { - error.MalformedDER => return error.ServerMalformedResponse, - else => |e| return e, - }; - } - }.f, - }; - - const cert_length = try reader.readIntBig(u24); - asn1.der.parse_schema(schema, captures, reader) catch |err| switch (err) { - error.InvalidLength, - error.InvalidTag, - error.InvalidContainerLength, - error.DoesNotMatchSchema, - => return error.ServerMalformedResponse, - else => |e| return e, - }; - errdefer capture_state.pub_key.deinit(allocator); - - try reader.skipBytes(length - cert_length - 3, .{}); - return capture_state.pub_key; -} - -pub const curves = struct { - pub const x25519 = struct { - pub const name = "x25519"; - const tag = 0x001D; - const pub_key_len = 32; - const Keys = std.crypto.dh.X25519.KeyPair; - - inline fn make_key_pair(rand: *std.rand.Random) Keys { - while (true) { - var seed: [32]u8 = undefined; - rand.bytes(&seed); - return std.crypto.dh.X25519.KeyPair.create(seed) catch continue; - } else unreachable; - } - - inline fn make_pre_master_secret( - key_pair: Keys, - pre_master_secret_buf: []u8, - server_public_key: *const [32]u8, - ) ![]const u8 { - pre_master_secret_buf[0..32].* = std.crypto.dh.X25519.scalarmult( - key_pair.secret_key, - server_public_key.*, - ) catch return error.PreMasterGenerationFailed; - return pre_master_secret_buf[0..32]; - } - }; - - pub const secp384r1 = struct { - pub const name = "secp384r1"; - const tag = 0x0018; - const pub_key_len = 97; - const Keys = crypto.ecc.KeyPair(crypto.ecc.SECP384R1); - - inline fn make_key_pair(rand: *std.rand.Random) Keys { - var seed: [48]u8 = undefined; - rand.bytes(&seed); - return crypto.ecc.make_key_pair(crypto.ecc.SECP384R1, seed); - } - - inline fn make_pre_master_secret( - key_pair: Keys, - pre_master_secret_buf: []u8, - server_public_key: *const [97]u8, - ) ![]const u8 { - pre_master_secret_buf[0..96].* = crypto.ecc.scalarmult( - crypto.ecc.SECP384R1, - server_public_key[1..].*, - &key_pair.secret_key, - ) catch return error.PreMasterGenerationFailed; - return pre_master_secret_buf[0..48]; - } - }; - - pub const secp256r1 = struct { - pub const name = "secp256r1"; - const tag = 0x0017; - const pub_key_len = 65; - const Keys = crypto.ecc.KeyPair(crypto.ecc.SECP256R1); - - inline fn make_key_pair(rand: *std.rand.Random) Keys { - var seed: [32]u8 = undefined; - rand.bytes(&seed); - return crypto.ecc.make_key_pair(crypto.ecc.SECP256R1, seed); - } - - inline fn make_pre_master_secret( - key_pair: Keys, - pre_master_secret_buf: []u8, - server_public_key: *const [65]u8, - ) ![]const u8 { - pre_master_secret_buf[0..64].* = crypto.ecc.scalarmult( - crypto.ecc.SECP256R1, - server_public_key[1..].*, - &key_pair.secret_key, - ) catch return error.PreMasterGenerationFailed; - return pre_master_secret_buf[0..32]; - } - }; - - pub const all = &[_]type{ x25519, secp384r1, secp256r1 }; - - fn max_pub_key_len(comptime list: anytype) usize { - var max: usize = 0; - for (list) |curve| { - if (curve.pub_key_len > max) - max = curve.pub_key_len; - } - return max; - } - - fn max_pre_master_secret_len(comptime list: anytype) usize { - var max: usize = 0; - for (list) |curve| { - const curr = @typeInfo(std.meta.fieldInfo(curve.Keys, .public_key).field_type).Array.len; - if (curr > max) - max = curr; - } - return max; - } - - fn KeyPair(comptime list: anytype) type { - var fields: [list.len]std.builtin.TypeInfo.UnionField = undefined; - for (list) |curve, i| { - fields[i] = .{ - .name = curve.name, - .field_type = curve.Keys, - .alignment = @alignOf(curve.Keys), - }; - } - return @Type(.{ - .Union = .{ - .layout = .Extern, - .tag_type = null, - .fields = &fields, - .decls = &[0]std.builtin.TypeInfo.Declaration{}, - }, - }); - } - - inline fn make_key_pair(comptime list: anytype, curve_id: u16, rand: *std.rand.Random) KeyPair(list) { - inline for (list) |curve| { - if (curve.tag == curve_id) { - return @unionInit(KeyPair(list), curve.name, curve.make_key_pair(rand)); - } - } - unreachable; - } - - inline fn make_pre_master_secret( - comptime list: anytype, - curve_id: u16, - key_pair: KeyPair(list), - pre_master_secret_buf: *[max_pre_master_secret_len(list)]u8, - server_public_key: [max_pub_key_len(list)]u8, - ) ![]const u8 { - inline for (list) |curve| { - if (curve.tag == curve_id) { - return try curve.make_pre_master_secret( - @field(key_pair, curve.name), - pre_master_secret_buf, - server_public_key[0..curve.pub_key_len], - ); - } - } - unreachable; - } -}; - -pub fn client_connect( - options: anytype, - hostname: []const u8, -) ClientConnectError( - options.cert_verifier, - @TypeOf(options.reader), - @TypeOf(options.writer), -)!Client( - @TypeOf(options.reader), - @TypeOf(options.writer), - if (@hasField(@TypeOf(options), "ciphersuites")) - options.ciphersuites - else - ciphersuites.all, - @hasField(@TypeOf(options), "protocols"), -) { - const Options = @TypeOf(options); - if (@TypeOf(options.cert_verifier) != CertificateVerifier and - @TypeOf(options.cert_verifier) != @Type(.EnumLiteral)) - @compileError("cert_verifier should be of type CertificateVerifier"); - - if (!@hasField(Options, "temp_allocator")) - @compileError("Option tuple is missing field 'temp_allocator'"); - if (options.cert_verifier == .default) { - if (!@hasField(Options, "trusted_certificates")) - @compileError("Option tuple is missing field 'trusted_certificates' for .default cert_verifier"); - } - - const suites = if (!@hasField(Options, "ciphersuites")) - ciphersuites.all - else - options.ciphersuites; - if (suites.len == 0) - @compileError("Must provide at least one ciphersuite type."); - - const curvelist = if (!@hasField(Options, "curves")) - curves.all - else - options.curves; - if (curvelist.len == 0) - @compileError("Must provide at least one curve type."); - - const has_alpn = comptime @hasField(Options, "protocols"); - var handshake_record_hash = Sha256.init(.{}); - const reader = options.reader; - const writer = options.writer; - const hashing_reader = sha256_reader(&handshake_record_hash, reader); - const hashing_writer = sha256_writer(&handshake_record_hash, writer); - - var client_random: [32]u8 = undefined; - const rand = if (!@hasField(Options, "rand")) - std.crypto.random - else - options.rand; - - rand.bytes(&client_random); - - var server_random: [32]u8 = undefined; - const ciphersuite_bytes = 2 * suites.len + 2; - const alpn_bytes = if (has_alpn) blk: { - var sum: usize = 0; - for (options.protocols) |proto| { - sum += proto.len; - } - break :blk 6 + options.protocols.len + sum; - } else 0; - const curvelist_bytes = 2 * curvelist.len; - var protocol: if (has_alpn) []const u8 else void = undefined; - { - const client_hello_start = comptime blk: { - // TODO: We assume the compiler is running in a little endian system - var starting_part: [46]u8 = [_]u8{ - // Record header: Handshake record type, protocol version, handshake size - 0x16, 0x03, 0x01, undefined, undefined, - // Handshake message type, bytes of client hello - 0x01, undefined, undefined, undefined, - // Client version (hardcoded to TLS 1.2 even for TLS 1.3) - 0x03, - 0x03, - } ++ ([1]u8{undefined} ** 32) ++ [_]u8{ - // Session ID - 0x00, - } ++ mem.toBytes(@byteSwap(u16, ciphersuite_bytes)); - // using .* = mem.asBytes(...).* or mem.writeIntBig didn't work... - - // Same as above, couldnt achieve this with a single buffer. - // TLS_EMPTY_RENEGOTIATION_INFO_SCSV - var ciphersuite_buf: []const u8 = &[2]u8{ 0x00, 0x0f }; - for (suites) |cs, i| { - // Also check for properties of the ciphersuites here - if (cs.key_exchange != .ecdhe) - @compileError("Non ECDHE key exchange is not supported yet."); - if (cs.hash != .sha256) - @compileError("Non SHA256 hash algorithm is not supported yet."); - - ciphersuite_buf = ciphersuite_buf ++ mem.toBytes(@byteSwap(u16, cs.tag)); - } - - var ending_part: [13]u8 = [_]u8{ - // Compression methods (no compression) - 0x01, 0x00, - // Extensions length - undefined, undefined, - // Extension: server name - // id, length, length of entry - 0x00, 0x00, - undefined, undefined, - undefined, undefined, - // entry type, length of bytes - 0x00, undefined, - undefined, - }; - break :blk starting_part ++ ciphersuite_buf ++ ending_part; - }; - - var msg_buf = client_hello_start.ptr[0..client_hello_start.len].*; - mem.writeIntBig(u16, msg_buf[3..5], @intCast(u16, alpn_bytes + hostname.len + 0x55 + ciphersuite_bytes + curvelist_bytes)); - mem.writeIntBig(u24, msg_buf[6..9], @intCast(u24, alpn_bytes + hostname.len + 0x51 + ciphersuite_bytes + curvelist_bytes)); - mem.copy(u8, msg_buf[11..43], &client_random); - mem.writeIntBig(u16, msg_buf[48 + ciphersuite_bytes ..][0..2], @intCast(u16, alpn_bytes + hostname.len + 0x28 + curvelist_bytes)); - mem.writeIntBig(u16, msg_buf[52 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 5)); - mem.writeIntBig(u16, msg_buf[54 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 3)); - mem.writeIntBig(u16, msg_buf[57 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len)); - try writer.writeAll(msg_buf[0..5]); - try hashing_writer.writeAll(msg_buf[5..]); - } - try hashing_writer.writeAll(hostname); - if (has_alpn) { - var msg_buf = [6]u8{ 0x00, 0x10, undefined, undefined, undefined, undefined }; - mem.writeIntBig(u16, msg_buf[2..4], @intCast(u16, alpn_bytes - 4)); - mem.writeIntBig(u16, msg_buf[4..6], @intCast(u16, alpn_bytes - 6)); - try hashing_writer.writeAll(&msg_buf); - for (options.protocols) |proto| { - try hashing_writer.writeByte(@intCast(u8, proto.len)); - try hashing_writer.writeAll(proto); - } - } - - // Extension: supported groups - { - var msg_buf = [6]u8{ - 0x00, 0x0A, - undefined, undefined, - undefined, undefined, - }; - - mem.writeIntBig(u16, msg_buf[2..4], @intCast(u16, curvelist_bytes + 2)); - mem.writeIntBig(u16, msg_buf[4..6], @intCast(u16, curvelist_bytes)); - try hashing_writer.writeAll(&msg_buf); - - inline for (curvelist) |curve| { - try hashing_writer.writeIntBig(u16, curve.tag); - } - } - - try hashing_writer.writeAll(&[25]u8{ - // Extension: EC point formats => uncompressed point format - 0x00, 0x0B, 0x00, 0x02, 0x01, 0x00, - // Extension: Signature algorithms - // RSA/PKCS1/SHA256, RSA/PKCS1/SHA512 - 0x00, 0x0D, 0x00, 0x06, 0x00, 0x04, - 0x04, 0x01, 0x06, 0x01, - // Extension: Renegotiation Info => new connection - 0xFF, 0x01, - 0x00, 0x01, 0x00, - // Extension: SCT (signed certificate timestamp) - 0x00, 0x12, 0x00, - 0x00, - }); - - // Read server hello - var ciphersuite: u16 = undefined; - { - const length = try handshake_record_length(reader); - if (length < 44) - return error.ServerMalformedResponse; - { - var hs_hdr_and_server_ver: [6]u8 = undefined; - try hashing_reader.readNoEof(&hs_hdr_and_server_ver); - if (hs_hdr_and_server_ver[0] != 0x02) - return error.ServerMalformedResponse; - if (!mem.eql(u8, hs_hdr_and_server_ver[4..6], "\x03\x03")) - return error.ServerInvalidVersion; - } - try hashing_reader.readNoEof(&server_random); - - // Just skip the session id for now - const sess_id_len = try hashing_reader.readByte(); - if (sess_id_len != 0) - try hashing_reader.skipBytes(sess_id_len, .{}); - - { - ciphersuite = try hashing_reader.readIntBig(u16); - var found = false; - inline for (suites) |cs| { - if (ciphersuite == cs.tag) { - found = true; - // TODO This segfaults stage1 - // break; - } - } - if (!found) - return error.ServerInvalidCipherSuite; - } - - // Compression method - if ((try hashing_reader.readByte()) != 0x00) - return error.ServerInvalidCompressionMethod; - - const exts_length = try hashing_reader.readIntBig(u16); - var ext_byte_idx: usize = 0; - while (ext_byte_idx < exts_length) { - var ext_tag: [2]u8 = undefined; - try hashing_reader.readNoEof(&ext_tag); - - const ext_len = try hashing_reader.readIntBig(u16); - ext_byte_idx += 4 + ext_len; - if (ext_tag[0] == 0xFF and ext_tag[1] == 0x01) { - // Renegotiation info - const renegotiation_info = try hashing_reader.readByte(); - if (ext_len != 0x01 or renegotiation_info != 0x00) - return error.ServerInvalidRenegotiationData; - } else if (ext_tag[0] == 0x00 and ext_tag[1] == 0x00) { - // Server name - if (ext_len != 0) - try hashing_reader.skipBytes(ext_len, .{}); - } else if (ext_tag[0] == 0x00 and ext_tag[1] == 0x0B) { - const format_count = try hashing_reader.readByte(); - var found_uncompressed = false; - var i: usize = 0; - while (i < format_count) : (i += 1) { - const byte = try hashing_reader.readByte(); - if (byte == 0x0) - found_uncompressed = true; - } - if (!found_uncompressed) - return error.ServerInvalidECPointCompression; - } else if (has_alpn and ext_tag[0] == 0x00 and ext_tag[1] == 0x10) { - const alpn_ext_len = try hashing_reader.readIntBig(u16); - if (alpn_ext_len != ext_len - 2) - return error.ServerMalformedResponse; - const str_len = try hashing_reader.readByte(); - var buf: [256]u8 = undefined; - try hashing_reader.readNoEof(buf[0..str_len]); - const found = for (options.protocols) |proto| { - if (mem.eql(u8, proto, buf[0..str_len])) { - protocol = proto; - break true; - } - } else false; - if (!found) - return error.ServerInvalidProtocol; - try hashing_reader.skipBytes(alpn_ext_len - str_len - 1, .{}); - } else return error.ServerInvalidExtension; - } - if (ext_byte_idx != exts_length) - return error.ServerMalformedResponse; - } - // Read server certificates - var certificate_public_key: x509.PublicKey = undefined; - { - const length = try handshake_record_length(reader); - { - var handshake_header: [4]u8 = undefined; - try hashing_reader.readNoEof(&handshake_header); - if (handshake_header[0] != 0x0b) - return error.ServerMalformedResponse; - } - const certs_length = try hashing_reader.readIntBig(u24); - const cert_verifier: CertificateVerifier = options.cert_verifier; - switch (cert_verifier) { - .none => certificate_public_key = try extract_cert_public_key( - options.temp_allocator, - hashing_reader, - certs_length, - ), - .function => |f| { - var reader_state = CertificateReaderState(@TypeOf(hashing_reader)){ - .reader = hashing_reader, - .length = certs_length, - }; - var cert_reader = CertificateReader(@TypeOf(hashing_reader)){ .context = &reader_state }; - certificate_public_key = try f(cert_reader); - try hashing_reader.skipBytes(reader_state.length - reader_state.idx, .{}); - }, - .default => certificate_public_key = try default_cert_verifier( - options.temp_allocator, - hashing_reader, - certs_length, - options.trusted_certificates, - hostname, - ), - } - } - errdefer certificate_public_key.deinit(options.temp_allocator); - // Read server ephemeral public key - var server_public_key_buf: [curves.max_pub_key_len(curvelist)]u8 = undefined; - var curve_id: u16 = undefined; - var curve_id_buf: [3]u8 = undefined; - var pub_key_len: u8 = undefined; - { - const length = try handshake_record_length(reader); - { - var handshake_header: [4]u8 = undefined; - try hashing_reader.readNoEof(&handshake_header); - if (handshake_header[0] != 0x0c) - return error.ServerMalformedResponse; - - try hashing_reader.readNoEof(&curve_id_buf); - if (curve_id_buf[0] != 0x03) - return error.ServerMalformedResponse; - - curve_id = mem.readIntBig(u16, curve_id_buf[1..]); - var found = false; - inline for (curvelist) |curve| { - if (curve.tag == curve_id) { - found = true; - // @TODO This break segfaults stage1 - // break; - } - } - if (!found) - return error.ServerInvalidCurve; - } - - pub_key_len = try hashing_reader.readByte(); - inline for (curvelist) |curve| { - if (curve.tag == curve_id) { - if (curve.pub_key_len != pub_key_len) - return error.ServerMalformedResponse; - // @TODO This break segfaults stage1 - // break; - } - } - - try hashing_reader.readNoEof(server_public_key_buf[0..pub_key_len]); - if (curve_id != curves.x25519.tag) { - if (server_public_key_buf[0] != 0x04) - return error.ServerMalformedResponse; - } - - // Signed public key - const signature_id = try hashing_reader.readIntBig(u16); - const signature_len = try hashing_reader.readIntBig(u16); - - var hash_buf: [64]u8 = undefined; - var hash: []const u8 = undefined; - const signature_algoritm: SignatureAlgorithm = switch (signature_id) { - // RSA/PKCS1/SHA256 - 0x0401 => block: { - var sha256 = Sha256.init(.{}); - sha256.update(&client_random); - sha256.update(&server_random); - sha256.update(&curve_id_buf); - // @TODO Should this always be \x20 instead? - sha256.update(&[1]u8{pub_key_len}); - sha256.update(server_public_key_buf[0..pub_key_len]); - sha256.final(hash_buf[0..32]); - hash = hash_buf[0..32]; - break :block .rsa_sha256; - }, - // RSA/PKCS1/SHA512 - 0x0601 => block: { - var sha512 = Sha512.init(.{}); - sha512.update(&client_random); - sha512.update(&server_random); - sha512.update(&curve_id_buf); - sha512.update(&[1]u8{pub_key_len}); - sha512.update(server_public_key_buf[0..pub_key_len]); - sha512.final(hash_buf[0..64]); - hash = hash_buf[0..64]; - break :block .rsa_sha512; - }, - else => return error.ServerInvalidSignatureAlgorithm, - }; - const signature_bytes = try options.temp_allocator.alloc(u8, signature_len); - defer options.temp_allocator.free(signature_bytes); - try hashing_reader.readNoEof(signature_bytes); - - if (!try verify_signature( - options.temp_allocator, - signature_algoritm, - .{ .data = signature_bytes, .bit_len = signature_len * 8 }, - hash, - certificate_public_key, - )) - return error.ServerInvalidSignature; - - certificate_public_key.deinit(options.temp_allocator); - certificate_public_key = x509.PublicKey.empty; - } - // Read server hello done - { - const length = try handshake_record_length(reader); - const is_bytes = try hashing_reader.isBytes("\x0e\x00\x00\x00"); - if (length != 4 or !is_bytes) - return error.ServerMalformedResponse; - } - - // Generate keys for the session - const client_key_pair = curves.make_key_pair(curvelist, curve_id, rand); - - // Client key exchange - try writer.writeAll(&[3]u8{ 0x16, 0x03, 0x03 }); - try writer.writeIntBig(u16, pub_key_len + 5); - try hashing_writer.writeAll(&[5]u8{ 0x10, 0x00, 0x00, pub_key_len + 1, pub_key_len }); - - inline for (curvelist) |curve| { - if (curve.tag == curve_id) { - const actual_len = @typeInfo(std.meta.fieldInfo(curve.Keys, .public_key).field_type).Array.len; - if (pub_key_len == actual_len + 1) { - try hashing_writer.writeByte(0x04); - } else { - std.debug.assert(pub_key_len == actual_len); - } - try hashing_writer.writeAll(&@field(client_key_pair, curve.name).public_key); - break; - } - } - - // Client encryption keys calculation for ECDHE_RSA cipher suites with SHA256 hash - var master_secret: [48]u8 = undefined; - var key_data: ciphers.KeyData(suites) = undefined; - { - var pre_master_secret_buf: [curves.max_pre_master_secret_len(curvelist)]u8 = undefined; - const pre_master_secret = try curves.make_pre_master_secret( - curvelist, - curve_id, - client_key_pair, - &pre_master_secret_buf, - server_public_key_buf, - ); - - var seed: [77]u8 = undefined; - seed[0..13].* = "master secret".*; - seed[13..45].* = client_random; - seed[45..77].* = server_random; - - var a1: [32 + seed.len]u8 = undefined; - Hmac256.create(a1[0..32], &seed, pre_master_secret); - var a2: [32 + seed.len]u8 = undefined; - Hmac256.create(a2[0..32], a1[0..32], pre_master_secret); - - a1[32..].* = seed; - a2[32..].* = seed; - - var p1: [32]u8 = undefined; - Hmac256.create(&p1, &a1, pre_master_secret); - var p2: [32]u8 = undefined; - Hmac256.create(&p2, &a2, pre_master_secret); - - master_secret[0..32].* = p1; - master_secret[32..48].* = p2[0..16].*; - - // Key expansion - seed[0..13].* = "key expansion".*; - seed[13..45].* = server_random; - seed[45..77].* = client_random; - a1[32..].* = seed; - a2[32..].* = seed; - - const KeyExpansionState = struct { - seed: *const [77]u8, - a1: *[32 + seed.len]u8, - a2: *[32 + seed.len]u8, - master_secret: *const [48]u8, - }; - - const next_32_bytes = struct { - inline fn f( - state: *KeyExpansionState, - comptime chunk_idx: comptime_int, - chunk: *[32]u8, - ) void { - if (chunk_idx == 0) { - Hmac256.create(state.a1[0..32], state.seed, state.master_secret); - Hmac256.create(chunk, state.a1, state.master_secret); - } else if (chunk_idx % 2 == 1) { - Hmac256.create(state.a2[0..32], state.a1[0..32], state.master_secret); - Hmac256.create(chunk, state.a2, state.master_secret); - } else { - Hmac256.create(state.a1[0..32], state.a2[0..32], state.master_secret); - Hmac256.create(chunk, state.a1, state.master_secret); - } - } - }.f; - var state = KeyExpansionState{ - .seed = &seed, - .a1 = &a1, - .a2 = &a2, - .master_secret = &master_secret, - }; - - key_data = ciphers.key_expansion(suites, ciphersuite, &state, next_32_bytes); - } - - // Client change cipher spec and client handshake finished - { - try writer.writeAll(&[6]u8{ - // Client change cipher spec - 0x14, 0x03, 0x03, - 0x00, 0x01, 0x01, - }); - // The message we need to encrypt is the following: - // 0x14 0x00 0x00 0x0c - // <12 bytes of verify_data> - // seed = "client finished" + SHA256(all handshake messages) - // a1 = HMAC-SHA256(key=MasterSecret, data=seed) - // p1 = HMAC-SHA256(key=MasterSecret, data=a1 + seed) - // verify_data = p1[0..12] - var verify_message: [16]u8 = undefined; - verify_message[0..4].* = "\x14\x00\x00\x0C".*; - { - var seed: [47]u8 = undefined; - seed[0..15].* = "client finished".*; - // We still need to update the hash one time, so we copy - // to get the current digest here. - var hash_copy = handshake_record_hash; - hash_copy.final(seed[15..47]); - - var a1: [32 + seed.len]u8 = undefined; - Hmac256.create(a1[0..32], &seed, &master_secret); - a1[32..].* = seed; - var p1: [32]u8 = undefined; - Hmac256.create(&p1, &a1, &master_secret); - verify_message[4..16].* = p1[0..12].*; - } - handshake_record_hash.update(&verify_message); - - inline for (suites) |cs| { - if (cs.tag == ciphersuite) { - try cs.raw_write( - 256, - rand, - &key_data, - writer, - [3]u8{ 0x16, 0x03, 0x03 }, - 0, - &verify_message, - ); - } - } - } - - // Server change cipher spec - { - const length = try record_length(0x14, reader); - const next_byte = try reader.readByte(); - if (length != 1 or next_byte != 0x01) - return error.ServerMalformedResponse; - } - // Server handshake finished - { - const length = try handshake_record_length(reader); - - var verify_message: [16]u8 = undefined; - verify_message[0..4].* = "\x14\x00\x00\x0C".*; - { - var seed: [47]u8 = undefined; - seed[0..15].* = "server finished".*; - handshake_record_hash.final(seed[15..47]); - var a1: [32 + seed.len]u8 = undefined; - Hmac256.create(a1[0..32], &seed, &master_secret); - a1[32..].* = seed; - var p1: [32]u8 = undefined; - Hmac256.create(&p1, &a1, &master_secret); - verify_message[4..16].* = p1[0..12].*; - } - - inline for (suites) |cs| { - if (cs.tag == ciphersuite) { - if (!try cs.check_verify_message(&key_data, length, reader, verify_message)) - return error.ServerInvalidVerifyData; - } - } - } - - return Client(@TypeOf(reader), @TypeOf(writer), suites, has_alpn){ - .ciphersuite = ciphersuite, - .key_data = key_data, - .state = ciphers.client_state_default(suites, ciphersuite), - .rand = rand, - .parent_reader = reader, - .parent_writer = writer, - .protocol = protocol, - }; -} - -pub fn Client( - comptime _Reader: type, - comptime _Writer: type, - comptime _ciphersuites: anytype, - comptime has_protocol: bool, -) type { - return struct { - const ReaderError = _Reader.Error || ServerAlert || error{ ServerMalformedResponse, ServerInvalidVersion }; - pub const Reader = std.io.Reader(*@This(), ReaderError, read); - pub const Writer = std.io.Writer(*@This(), _Writer.Error, write); - - ciphersuite: u16, - client_seq: u64 = 1, - server_seq: u64 = 1, - key_data: ciphers.KeyData(_ciphersuites), - state: ciphers.ClientState(_ciphersuites), - rand: *std.rand.Random, - - parent_reader: _Reader, - parent_writer: _Writer, - - protocol: if (has_protocol) []const u8 else void, - - pub fn reader(self: *@This()) Reader { - return .{ .context = self }; - } - - pub fn writer(self: *@This()) Writer { - return .{ .context = self }; - } - - pub fn read(self: *@This(), buffer: []u8) ReaderError!usize { - inline for (_ciphersuites) |cs| { - if (self.ciphersuite == cs.tag) { - // @TODO Make this buffer size configurable - return try cs.read( - 1024, - &@field(self.state, cs.name), - &self.key_data, - self.parent_reader, - &self.server_seq, - buffer, - ); - } - } - unreachable; - } - - pub fn write(self: *@This(), buffer: []const u8) _Writer.Error!usize { - if (buffer.len == 0) return 0; - - inline for (_ciphersuites) |cs| { - if (self.ciphersuite == cs.tag) { - // @TODO Make this buffer size configurable - const curr_bytes = @truncate(u16, std.math.min(buffer.len, 1024)); - try cs.raw_write( - 1024, - self.rand, - &self.key_data, - self.parent_writer, - [3]u8{ 0x17, 0x03, 0x03 }, - self.client_seq, - buffer[0..curr_bytes], - ); - self.client_seq += 1; - return curr_bytes; - } - } - unreachable; - } - - pub fn close_notify(self: *@This()) !void { - inline for (_ciphersuites) |cs| { - if (self.ciphersuite == cs.tag) { - try cs.raw_write( - 1024, - self.rand, - &self.key_data, - self.parent_writer, - [3]u8{ 0x15, 0x03, 0x03 }, - self.client_seq, - "\x01\x00", - ); - self.client_seq += 1; - return; - } - } - unreachable; - } - }; -} - -test "HTTPS request on wikipedia main page" { - const sock = try std.net.tcpConnectToHost(std.testing.allocator, "en.wikipedia.org", 443); - defer sock.close(); - - var fbs = std.io.fixedBufferStream(@embedFile("../test/DigiCertHighAssuranceEVRootCA.crt.pem")); - var trusted_chain = try x509.TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader()); - defer trusted_chain.deinit(); - - // @TODO Remove this once std.crypto.rand works in .evented mode - var rand = blk: { - var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined; - try std.os.getrandom(&seed); - break :blk &std.rand.DefaultCsprng.init(seed).random; - }; - - var client = try client_connect(.{ - .rand = rand, - .reader = sock.reader(), - .writer = sock.writer(), - .cert_verifier = .default, - .temp_allocator = std.testing.allocator, - .trusted_certificates = trusted_chain.data.items, - .ciphersuites = .{ciphersuites.ECDHE_RSA_Chacha20_Poly1305}, - .protocols = &[_][]const u8{"http/1.1"}, - .curves = .{curves.x25519}, - }, "en.wikipedia.org"); - defer client.close_notify() catch {}; - - std.testing.expectEqualStrings("http/1.1", client.protocol); - try client.writer().writeAll("GET /wiki/Main_Page HTTP/1.1\r\nHost: en.wikipedia.org\r\nAccept: */*\r\n\r\n"); - - { - const header = try client.reader().readUntilDelimiterAlloc(std.testing.allocator, '\n', std.math.maxInt(usize)); - std.testing.expectEqualStrings("HTTP/1.1 200 OK", mem.trim(u8, header, &std.ascii.spaces)); - std.testing.allocator.free(header); - } - - // Skip the rest of the headers expect for Content-Length - var content_length: ?usize = null; - hdr_loop: while (true) { - const header = try client.reader().readUntilDelimiterAlloc(std.testing.allocator, '\n', std.math.maxInt(usize)); - defer std.testing.allocator.free(header); - - const hdr_contents = mem.trim(u8, header, &std.ascii.spaces); - if (hdr_contents.len == 0) { - break :hdr_loop; - } - - if (mem.startsWith(u8, hdr_contents, "Content-Length: ")) { - content_length = try std.fmt.parseUnsigned(usize, hdr_contents[16..], 10); - } - } - std.testing.expect(content_length != null); - const html_contents = try std.testing.allocator.alloc(u8, content_length.?); - defer std.testing.allocator.free(html_contents); - - try client.reader().readNoEof(html_contents); -} - -test "HTTPS request on twitch oath2 endpoint" { - const sock = try std.net.tcpConnectToHost(std.testing.allocator, "id.twitch.tv", 443); - defer sock.close(); - - // @TODO Remove this once std.crypto.rand works in .evented mode - var rand = blk: { - var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined; - try std.os.getrandom(&seed); - break :blk &std.rand.DefaultCsprng.init(seed).random; - }; - - var client = try client_connect(.{ - .rand = rand, - .temp_allocator = std.testing.allocator, - .reader = sock.reader(), - .writer = sock.writer(), - .cert_verifier = .none, - .protocols = &[_][]const u8{"http/1.1"}, - }, "id.twitch.tv"); - std.testing.expectEqualStrings("http/1.1", client.protocol); - defer client.close_notify() catch {}; - - try client.writer().writeAll("GET /oauth2/validate HTTP/1.1\r\nHost: id.twitch.tv\r\nAccept: */*\r\n\r\n"); - var content_length: ?usize = null; - hdr_loop: while (true) { - const header = try client.reader().readUntilDelimiterAlloc(std.testing.allocator, '\n', std.math.maxInt(usize)); - defer std.testing.allocator.free(header); - - const hdr_contents = mem.trim(u8, header, &std.ascii.spaces); - if (hdr_contents.len == 0) { - break :hdr_loop; - } - - if (mem.startsWith(u8, hdr_contents, "Content-Length: ")) { - content_length = try std.fmt.parseUnsigned(usize, hdr_contents[16..], 10); - } - } - std.testing.expect(content_length != null); - const html_contents = try std.testing.allocator.alloc(u8, content_length.?); - defer std.testing.allocator.free(html_contents); - - try client.reader().readNoEof(html_contents); -} - -test "Connecting to expired.badssl.com returns an error" { - const sock = try std.net.tcpConnectToHost(std.testing.allocator, "expired.badssl.com", 443); - defer sock.close(); - - var fbs = std.io.fixedBufferStream(@embedFile("../test/DigiCertGlobalRootCA.crt.pem")); - var trusted_chain = try x509.TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader()); - defer trusted_chain.deinit(); - - // @TODO Remove this once std.crypto.rand works in .evented mode - var rand = blk: { - var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined; - try std.os.getrandom(&seed); - break :blk &std.rand.DefaultCsprng.init(seed).random; - }; - - std.testing.expectError(error.CertificateVerificationFailed, client_connect(.{ - .rand = rand, - .reader = sock.reader(), - .writer = sock.writer(), - .cert_verifier = .default, - .temp_allocator = std.testing.allocator, - .trusted_certificates = trusted_chain.data.items, - }, "expired.badssl.com")); -} - -test "Connecting to wrong.host.badssl.com returns an error" { - const sock = try std.net.tcpConnectToHost(std.testing.allocator, "wrong.host.badssl.com", 443); - defer sock.close(); - - var fbs = std.io.fixedBufferStream(@embedFile("../test/DigiCertGlobalRootCA.crt.pem")); - var trusted_chain = try x509.TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader()); - defer trusted_chain.deinit(); - - // @TODO Remove this once std.crypto.rand works in .evented mode - var rand = blk: { - var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined; - try std.os.getrandom(&seed); - break :blk &std.rand.DefaultCsprng.init(seed).random; - }; - - std.testing.expectError(error.CertificateVerificationFailed, client_connect(.{ - .rand = rand, - .reader = sock.reader(), - .writer = sock.writer(), - .cert_verifier = .default, - .temp_allocator = std.testing.allocator, - .trusted_certificates = trusted_chain.data.items, - }, "wrong.host.badssl.com")); -} - -test "Connecting to self-signed.badssl.com returns an error" { - const sock = try std.net.tcpConnectToHost(std.testing.allocator, "self-signed.badssl.com", 443); - defer sock.close(); - - var fbs = std.io.fixedBufferStream(@embedFile("../test/DigiCertGlobalRootCA.crt.pem")); - var trusted_chain = try x509.TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader()); - defer trusted_chain.deinit(); - - // @TODO Remove this once std.crypto.rand works in .evented mode - var rand = blk: { - var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined; - try std.os.getrandom(&seed); - break :blk &std.rand.DefaultCsprng.init(seed).random; - }; - - std.testing.expectError(error.CertificateVerificationFailed, client_connect(.{ - .rand = rand, - .reader = sock.reader(), - .writer = sock.writer(), - .cert_verifier = .default, - .temp_allocator = std.testing.allocator, - .trusted_certificates = trusted_chain.data.items, - }, "self-signed.badssl.com")); -} diff --git a/libs/iguanatls/src/x509.zig b/libs/iguanatls/src/x509.zig deleted file mode 100644 index e5aa1589d1d5d363064e4dd759a8f037364b6c08..0000000000000000000000000000000000000000 --- a/libs/iguanatls/src/x509.zig +++ /dev/null @@ -1,721 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; -const mem = std.mem; -const trait = std.meta.trait; - -const asn1 = @import("asn1.zig"); - -// zig fmt: off -// http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 -// @TODO add backing integer, values -pub const CurveId = enum { - sect163k1, sect163r1, sect163r2, sect193r1, - sect193r2, sect233k1, sect233r1, sect239k1, - sect283k1, sect283r1, sect409k1, sect409r1, - sect571k1, sect571r1, secp160k1, secp160r1, - secp160r2, secp192k1, secp192r1, secp224k1, - secp224r1, secp256k1, secp256r1, secp384r1, - secp521r1,brainpoolP256r1, brainpoolP384r1, - brainpoolP512r1, curve25519, curve448, -}; -// zig fmt: on - -pub const PublicKey = union(enum) { - pub const empty = PublicKey{ .ec = .{ .id = undefined, .curve_point = &[0]u8{} } }; - - /// RSA public key - rsa: struct { - //Positive std.math.big.int.Const numbers. - modulus: []const usize, - exponent: []const usize, - }, - /// Elliptic curve public key - ec: struct { - id: CurveId, - /// Public curve point (uncompressed format) - curve_point: []const u8, - }, - - pub fn deinit(self: @This(), alloc: *Allocator) void { - switch (self) { - .rsa => |rsa| { - alloc.free(rsa.modulus); - alloc.free(rsa.exponent); - }, - .ec => |ec| alloc.free(ec.curve_point), - } - } - - pub fn eql(self: @This(), other: @This()) bool { - if (@as(std.meta.Tag(@This()), self) != @as(std.meta.Tag(@This()), other)) - return false; - switch (self) { - .rsa => |mod_exp| return mem.eql(usize, mod_exp.exponent, other.rsa.exponent) and - mem.eql(usize, mod_exp.modulus, other.rsa.modulus), - .ec => |ec| return ec.id == other.ec.id and mem.eql(u8, ec.curve_point, other.ec.curve_point), - } - } -}; - -pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey { - if ((try reader.readByte()) != 0x30) - return error.MalformedDER; - const seq_len = try asn1.der.parse_length(reader); - - if ((try reader.readByte()) != 0x06) - return error.MalformedDER; - const oid_bytes = try asn1.der.parse_length(reader); - if (oid_bytes == 9) { - // @TODO This fails in async if merged with the if - if (!try reader.isBytes(&[9]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1 })) - return error.MalformedDER; - // OID is 1.2.840.113549.1.1.1 - // RSA key - // Skip past the NULL - const null_byte = try reader.readByte(); - if (null_byte != 0x05) - return error.MalformedDER; - const null_len = try asn1.der.parse_length(reader); - if (null_len != 0x00) - return error.MalformedDER; - { - // BitString next! - if ((try reader.readByte()) != 0x03) - return error.MalformedDER; - _ = try asn1.der.parse_length(reader); - const bit_string_unused_bits = try reader.readByte(); - if (bit_string_unused_bits != 0) - return error.MalformedDER; - - if ((try reader.readByte()) != 0x30) - return error.MalformedDER; - _ = try asn1.der.parse_length(reader); - - // Modulus - if ((try reader.readByte()) != 0x02) - return error.MalformedDER; - const modulus = try asn1.der.parse_int(allocator, reader); - errdefer allocator.free(modulus.limbs); - if (!modulus.positive) return error.MalformedDER; - // Exponent - if ((try reader.readByte()) != 0x02) - return error.MalformedDER; - const exponent = try asn1.der.parse_int(allocator, reader); - errdefer allocator.free(exponent.limbs); - if (!exponent.positive) return error.MalformedDER; - return PublicKey{ - .rsa = .{ - .modulus = modulus.limbs, - .exponent = exponent.limbs, - }, - }; - } - } else if (oid_bytes == 7) { - // @TODO This fails in async if merged with the if - if (!try reader.isBytes(&[7]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 })) - return error.MalformedDER; - // OID is 1.2.840.10045.2.1 - // Elliptical curve - // We only support named curves, for which the parameter field is an OID. - const oid_tag = try reader.readByte(); - if (oid_tag != 0x06) - return error.MalformedDER; - const curve_oid_bytes = try asn1.der.parse_length(reader); - - var key: PublicKey = undefined; - if (curve_oid_bytes == 5) { - if (!try reader.isBytes(&[4]u8{ 0x2B, 0x81, 0x04, 0x00 })) - return error.MalformedDER; - // 1.3.132.0.{34, 35} - const last_byte = try reader.readByte(); - if (last_byte == 0x22) - key = .{ .ec = .{ .id = .secp384r1, .curve_point = undefined } } - else if (last_byte == 0x23) - key = .{ .ec = .{ .id = .secp521r1, .curve_point = undefined } } - else - return error.MalformedDER; - } else if (curve_oid_bytes == 8) { - if (!try reader.isBytes(&[8]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x3, 0x1, 0x7 })) - return error.MalformedDER; - key = .{ .ec = .{ .id = .secp256r1, .curve_point = undefined } }; - } else { - return error.MalformedDER; - } - - if ((try reader.readByte()) != 0x03) - return error.MalformedDER; - const byte_len = try asn1.der.parse_length(reader); - const unused_bits = try reader.readByte(); - const bit_count = (byte_len - 1) * 8 - unused_bits; - if (bit_count % 8 != 0) - return error.MalformedDER; - const bit_memory = try allocator.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable); - errdefer allocator.free(bit_memory); - try reader.readNoEof(bit_memory[0 .. byte_len - 1]); - - key.ec.curve_point = bit_memory; - return key; - } - return error.MalformedDER; -} - -pub fn DecodeDERError(comptime Reader: type) type { - return Reader.Error || error{ - MalformedPEM, - MalformedDER, - EndOfStream, - OutOfMemory, - }; -} - -pub const TrustAnchor = struct { - /// Subject distinguished name - dn: []const u8, - /// A "CA" anchor is deemed fit to verify signatures on certificates. - /// A "non-CA" anchor is accepted only for direct trust (server's certificate - /// name and key match the anchor). - is_ca: bool = false, - public_key: PublicKey, - - const CaptureState = struct { - self: *TrustAnchor, - allocator: *Allocator, - dn_allocated: bool = false, - pk_allocated: bool = false, - }; - fn initSubjectDn(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void { - const dn_mem = try state.allocator.alloc(u8, length); - errdefer state.allocator.free(dn_mem); - try reader.readNoEof(dn_mem); - state.self.dn = dn_mem; - state.dn_allocated = true; - } - - fn processExtension(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void { - const object_id = try asn1.der.parse_value(state.allocator, reader); - defer object_id.deinit(state.allocator); - if (object_id != .object_identifier) return error.DoesNotMatchSchema; - if (object_id.object_identifier.len != 4) - return; - - const data = object_id.object_identifier.data; - // Basic constraints extension - if (data[0] != 2 or data[1] != 5 or data[2] != 29 or data[3] != 19) - return; - - const basic_constraints = try asn1.der.parse_value(state.allocator, reader); - defer basic_constraints.deinit(state.allocator); - - switch (basic_constraints) { - .bool => |b| state.self.is_ca = true, - .octet_string => |s| { - if (s.len != 5 or s[0] != 0x30 or s[1] != 0x03 or s[2] != 0x01 or s[3] != 0x01) - return error.DoesNotMatchSchema; - state.self.is_ca = s[4] != 0x00; - }, - else => return error.DoesNotMatchSchema, - } - } - - fn initExtensions(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void { - const schema = .{ - .sequence_of, - .{ .capture, 0, .sequence }, - }; - const captures = .{ - state, processExtension, - }; - try asn1.der.parse_schema(schema, captures, reader); - } - - fn initPublicKeyInfo(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void { - state.self.public_key = try parse_public_key(state.allocator, reader); - state.pk_allocated = true; - } - - /// Initialize a trusted anchor from distinguished encoding rules (DER) encoded data - pub fn create(allocator: *Allocator, der_reader: anytype) DecodeDERError(@TypeOf(der_reader))!@This() { - var self: @This() = undefined; - self.is_ca = false; - // https://tools.ietf.org/html/rfc5280#page-117 - const schema = .{ - .sequence, .{ - // tbsCertificate - .{ - .sequence, - .{ - .{ .context_specific, 0 }, // version - .{.int}, // serialNumber - .{.sequence}, // signature - .{.sequence}, // issuer - .{.sequence}, // validity, - .{ .capture, 0, .sequence }, // subject - .{ .capture, 1, .sequence }, // subjectPublicKeyInfo - .{ .optional, .context_specific, 1 }, // issuerUniqueID - .{ .optional, .context_specific, 2 }, // subjectUniqueID - .{ .capture, 2, .optional, .context_specific, 3 }, // extensions - }, - }, - // signatureAlgorithm - .{.sequence}, - // signatureValue - .{.bit_string}, - }, - }; - - var capture_state = CaptureState{ - .self = &self, - .allocator = allocator, - }; - const captures = .{ - &capture_state, initSubjectDn, - &capture_state, initPublicKeyInfo, - &capture_state, initExtensions, - }; - - errdefer { - if (capture_state.dn_allocated) - allocator.free(self.dn); - if (capture_state.pk_allocated) - self.public_key.deinit(allocator); - } - - asn1.der.parse_schema(schema, captures, der_reader) catch |err| switch (err) { - error.InvalidLength, - error.InvalidTag, - error.InvalidContainerLength, - error.DoesNotMatchSchema, - => return error.MalformedDER, - else => |e| return e, - }; - return self; - } - - pub fn deinit(self: @This(), alloc: *Allocator) void { - alloc.free(self.dn); - self.public_key.deinit(alloc); - } - - pub fn format(self: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - try writer.print( - \\CERTIFICATE - \\----------- - \\IS CA: {} - \\Subject distinguished name (encoded): - \\{X} - \\Public key: - \\ - , .{ self.is_ca, self.dn }); - - switch (self.public_key) { - .rsa => |mod_exp| { - const modulus = std.math.big.int.Const{ .positive = true, .limbs = mod_exp.modulus }; - const exponent = std.math.big.int.Const{ .positive = true, .limbs = mod_exp.exponent }; - try writer.print( - \\RSA - \\modulus: {} - \\exponent: {} - \\ - , .{ - modulus, - exponent, - }); - }, - .ec => |ec| { - try writer.print( - \\EC (Curve: {}) - \\point: {} - \\ - , .{ - ec.id, - ec.curve_point, - }); - }, - } - - try writer.writeAll( - \\----------- - \\ - ); - } -}; - -pub const TrustAnchorChain = struct { - data: std.ArrayList(TrustAnchor), - - pub fn from_pem(allocator: *Allocator, pem_reader: anytype) DecodeDERError(@TypeOf(pem_reader))!@This() { - var self = @This(){ .data = std.ArrayList(TrustAnchor).init(allocator) }; - errdefer self.deinit(); - - var it = pemCertificateIterator(pem_reader); - while (try it.next()) |cert_reader| { - var buffered = std.io.bufferedReader(cert_reader); - const anchor = try TrustAnchor.create(allocator, buffered.reader()); - errdefer anchor.deinit(allocator); - try self.data.append(anchor); - } - return self; - } - - pub fn deinit(self: @This()) void { - const alloc = self.data.allocator; - for (self.data.items) |ta| ta.deinit(alloc); - self.data.deinit(); - } -}; - -fn PEMSectionReader(comptime Reader: type) type { - const Error = Reader.Error || error{MalformedPEM}; - const read = struct { - fn f(it: *PEMCertificateIterator(Reader), buf: []u8) Error!usize { - var out_idx: usize = 0; - if (it.waiting_chars_len > 0) { - const rest_written = std.math.min(it.waiting_chars_len, buf.len); - while (out_idx < rest_written) : (out_idx += 1) { - buf[out_idx] = it.waiting_chars[out_idx]; - } - - it.waiting_chars_len -= rest_written; - if (it.waiting_chars_len != 0) { - std.mem.copy(u8, it.waiting_chars[0..], it.waiting_chars[rest_written..]); - } - - if (out_idx == buf.len) { - return out_idx; - } - } - if (it.state != .in_section) - return out_idx; - - var base64_buf: [4]u8 = undefined; - var base64_idx: usize = 0; - while (true) { - const byte = it.reader.readByte() catch |err| switch (err) { - error.EndOfStream => return out_idx, - else => |e| return e, - }; - - if (byte == '-') { - if (it.reader.isBytes("----END ") catch |err| switch (err) { - error.EndOfStream => return error.MalformedPEM, - else => |e| return e, - }) { - try it.reader.skipUntilDelimiterOrEof('\n'); - it.state = .none; - return out_idx; - } else return error.MalformedPEM; - } else if (byte == '\r') { - if ((it.reader.readByte() catch |err| switch (err) { - error.EndOfStream => return error.MalformedPEM, - else => |e| return e, - }) != '\n') - return error.MalformedPEM; - continue; - } else if (byte == '\n') - continue; - - base64_buf[base64_idx] = byte; - base64_idx += 1; - if (base64_idx == base64_buf.len) { - base64_idx = 0; - - const out_len = std.base64.standard_decoder.calcSize(&base64_buf) catch - return error.MalformedPEM; - - const rest_chars = if (out_len > buf.len - out_idx) - out_len - (buf.len - out_idx) - else - 0; - const buf_chars = out_len - rest_chars; - - var res_buffer: [3]u8 = undefined; - std.base64.standard_decoder_unsafe.decode(res_buffer[0..out_len], &base64_buf); - - var i: u3 = 0; - while (i < buf_chars) : (i += 1) { - buf[out_idx] = res_buffer[i]; - out_idx += 1; - } - - if (rest_chars > 0) { - mem.copy(u8, &it.waiting_chars, res_buffer[i..]); - it.waiting_chars_len = @intCast(u2, rest_chars); - } - if (out_idx == buf.len) - return out_idx; - } - } - } - }.f; - - return std.io.Reader( - *PEMCertificateIterator(Reader), - Error, - read, - ); -} - -fn PEMCertificateIterator(comptime Reader: type) type { - return struct { - pub const SectionReader = PEMSectionReader(Reader); - pub const NextError = SectionReader.Error || error{EndOfStream}; - - reader: Reader, - // Internal state for the iterator and the current reader. - state: enum { - none, - in_other, - in_section, - } = .none, - waiting_chars: [4]u8 = undefined, - waiting_chars_len: u2 = 0, - - // @TODO More verification, this will accept lots of invalid PEM - pub fn next(self: *@This()) NextError!?SectionReader { - self.waiting_chars_len = 0; - while (true) { - const byte = self.reader.readByte() catch |err| switch (err) { - error.EndOfStream => if (self.state == .none) - return null - else - return error.EndOfStream, - else => |e| return e, - }; - - switch (self.state) { - .none => switch (byte) { - '#' => { - try self.reader.skipUntilDelimiterOrEof('\n'); - continue; - }, - '\r', '\n', ' ', '\t' => continue, - '-' => { - if (try self.reader.isBytes("----BEGIN ")) { - const first_section_byte = try self.reader.readByte(); - if (first_section_byte == 'C') { - const rest = "ERTIFICATE"; - var matched: usize = 0; - while ((try self.reader.readByte()) == rest[matched]) : (matched += 1) { - if (matched == rest.len - 1) { - try self.reader.skipUntilDelimiterOrEof('\n'); - self.state = .in_section; - return SectionReader{ .context = self }; - } - } - try self.reader.skipUntilDelimiterOrEof('\n'); - self.state = .in_other; - continue; - } else if (first_section_byte == 'X') { - const rest = ".509 CERTIFICATE"; - var matched: usize = 0; - while ((try self.reader.readByte()) == rest[matched]) : (matched += 1) { - if (matched == rest.len - 1) { - try self.reader.skipUntilDelimiterOrEof('\n'); - self.state = .in_section; - return SectionReader{ .context = self }; - } - } - try self.reader.skipUntilDelimiterOrEof('\n'); - self.state = .in_other; - continue; - } else { - try self.reader.skipUntilDelimiterOrEof('\n'); - self.state = .in_other; - continue; - } - } else return error.MalformedPEM; - }, - else => return error.MalformedPEM, - }, - .in_other, .in_section => switch (byte) { - '#' => { - try self.reader.skipUntilDelimiterOrEof('\n'); - continue; - }, - '\r', '\n', ' ', '\t' => continue, - '-' => { - if (try self.reader.isBytes("----END ")) { - try self.reader.skipUntilDelimiterOrEof('\n'); - self.state = .none; - continue; - } else return error.MalformedPEM; - }, - // @TODO Make sure the character is base64 - else => continue, - }, - } - } - } - }; -} - -/// Iterator of io.Reader that each decode one certificate from the PEM reader. -/// Readers do not have to be fully consumed until end of stream, but they must be -/// read from in order. -/// Iterator.SectionReader is the type of the io.Reader, Iterator.NextError is the error -/// set of the next() function. -pub fn pemCertificateIterator(reader: anytype) PEMCertificateIterator(@TypeOf(reader)) { - return .{ .reader = reader }; -} - -pub const NameElement = struct { - // Encoded OID without tag - oid: asn1.ObjectIdentifier, - // Destination buffer - buf: []u8, - status: enum { - not_found, - found, - errored, - }, -}; - -const github_pem = @embedFile("../test/github.pem"); -const github_der = @embedFile("../test/github.der"); - -fn expected_pem_certificate_chain(bytes: []const u8, certs: []const []const u8) !void { - var fbs = std.io.fixedBufferStream(bytes); - - var it = pemCertificateIterator(fbs.reader()); - var idx: usize = 0; - while (try it.next()) |cert_reader| : (idx += 1) { - const result_bytes = try cert_reader.readAllAlloc(std.testing.allocator, std.math.maxInt(usize)); - defer std.testing.allocator.free(result_bytes); - std.testing.expectEqualSlices(u8, certs[idx], result_bytes); - } - if (idx != certs.len) { - std.debug.panic("Read {} certificates, wanted {}", .{ idx, certs.len }); - } - std.testing.expect((try it.next()) == null); -} - -fn expected_pem_certificate(bytes: []const u8, cert_bytes: []const u8) !void { - try expected_pem_certificate_chain(bytes, &[1][]const u8{cert_bytes}); -} - -test "pemCertificateIterator" { - try expected_pem_certificate(github_pem, github_der); - try expected_pem_certificate( - \\-----BEGIN BOGUS----- - \\-----END BOGUS----- - \\ - ++ - github_pem, - github_der, - ); - - try expected_pem_certificate_chain( - github_pem ++ - \\ - \\-----BEGIN BOGUS----- - \\-----END BOGUS----- - \\ - ++ github_pem, - &[2][]const u8{ github_der, github_der }, - ); - - try expected_pem_certificate_chain( - \\-----BEGIN BOGUS----- - \\-----END BOGUS----- - \\ - , - &[0][]const u8{}, - ); - - // Try reading byte by byte from a cert reader - { - var fbs = std.io.fixedBufferStream(github_pem ++ "\n# Some comment\n" ++ github_pem); - var it = pemCertificateIterator(fbs.reader()); - // Read a couple of bytes from the first reader, then skip to the next - { - const first_reader = (try it.next()) orelse return error.NoCertificate; - var first_few: [8]u8 = undefined; - const bytes = try first_reader.readAll(&first_few); - std.testing.expectEqual(first_few.len, bytes); - std.testing.expectEqualSlices(u8, github_der[0..bytes], &first_few); - } - - const next_reader = (try it.next()) orelse return error.NoCertificate; - var idx: usize = 0; - while (true) : (idx += 1) { - const byte = next_reader.readByte() catch |err| switch (err) { - error.EndOfStream => break, - else => |e| return e, - }; - if (github_der[idx] != byte) { - std.debug.panic("index {}: expected 0x{X}, found 0x{X}", .{ idx, github_der[idx], byte }); - } - } - std.testing.expectEqual(github_der.len, idx); - std.testing.expect((try it.next()) == null); - } -} - -test "TrustAnchorChain" { - var fbs = std.io.fixedBufferStream(github_pem ++ - \\ - \\# Hellenic Academic and Research Institutions RootCA 2011 - \\-----BEGIN CERTIFICATE----- - \\MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix - \\RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 - \\dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p - \\YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw - \\NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK - \\EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl - \\cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl - \\c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB - \\BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz - \\dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ - \\fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns - \\bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD - \\75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP - \\FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV - \\HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp - \\5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu - \\b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA - \\A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p - \\6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 - \\TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 - \\dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys - \\Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI - \\l7WdmplNsDz4SgCbZN2fOUvRJ9e4 - \\-----END CERTIFICATE----- - \\ - \\# ePKI Root Certification Authority - \\-----BEGIN CERTIFICATE----- - \\MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe - \\MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 - \\ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe - \\Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw - \\IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL - \\SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF - \\AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH - \\SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh - \\ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X - \\DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 - \\TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ - \\fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA - \\sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU - \\WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS - \\nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH - \\dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip - \\NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC - \\AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF - \\MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH - \\ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB - \\uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl - \\PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP - \\JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ - \\gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 - \\j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 - \\5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB - \\o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS - \\/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z - \\Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE - \\W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D - \\hNQ+IIX3Sj0rnP0qCglN6oH4EZw= - \\-----END CERTIFICATE----- - ); - const chain = try TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader()); - defer chain.deinit(); -} diff --git a/libs/iguanatls/test/DigiCertGlobalRootCA.crt.pem b/libs/iguanatls/test/DigiCertGlobalRootCA.crt.pem deleted file mode 100644 index fd4341df26631d749ae1bd891e5b8780dac8c8bf..0000000000000000000000000000000000000000 --- a/libs/iguanatls/test/DigiCertGlobalRootCA.crt.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- diff --git a/libs/iguanatls/test/DigiCertHighAssuranceEVRootCA.crt.pem b/libs/iguanatls/test/DigiCertHighAssuranceEVRootCA.crt.pem deleted file mode 100644 index 9e6810ab70cfa112fe066ec8b2122fbf76586317..0000000000000000000000000000000000000000 --- a/libs/iguanatls/test/DigiCertHighAssuranceEVRootCA.crt.pem +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j -ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 -LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug -RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm -+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW -PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM -xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB -Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 -hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg -EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA -FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec -nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z -eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF -hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 -Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe -vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep -+OkuE6N36B9K ------END CERTIFICATE----- diff --git a/libs/iguanatls/test/github.der b/libs/iguanatls/test/github.der deleted file mode 100644 index dae0196507d9166bbf9de6ed93ed5bd91d2a6f7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 969 zcmXqLVm@ln#I$w+GZP~dlK|727_}^JhuQtym7mtRsg@h?vTHrk&& ztLnL}Q0ws3DJJQQxMViOXD)Ntcx3yg{tGjftaOaDx_oEKHJjTd7E8R&_^LL2_gWe( zWby8^XKvxdy5w!Em&G4m((=PUDRAG9qi=3oOnS`rlw%^#5e>)C->0KGMe7P*nC|y2 z;KS3U1MC5c*hY*|CU8utq&? zd&l;QPp7z6SghtsIkZ15c52JPg{pGxu~Grt3PNWbcjs{jTI}#X&BV;ez_>WsAklyi z7{#*ujEw(TSb)i@&43@o7Y6ZJ4VZzHfh)yU1V#fRgNzj0^gGw*%(v-CW8(-{yshZ|Lo0pNehH&82Z8W| zU7xFN3a0j%{+jp4b?dyFK8D^qCcbdi>Dyd!?)Ky0%ED_;6{j%X3T>aIlJc!9?aaM7 z=4V%!Y|Nakd}3aOl6Haf<4||QM9KFNM_cv%48AQe6jMI&{86fy@#+0i(hF>VoQmr< z>`vh5)a7A|3EF<-C)bRNqVe;E_SKjrTkLgNQvXCvo9oE*Ox~A0j}8cg?>JE=G%@s( zN9K(^T1?UWE>{!`Z-3cUpcXqJVcs5ZaZdd$r{$b8PO_^XycF~OmEz6}p*c2l_Rss% i5HmaZ>>Kx0s_N+r%s(?U)rMSO`QxRY@Z$0p@?HQubYDLJ diff --git a/libs/iguanatls/test/github.pem b/libs/iguanatls/test/github.pem deleted file mode 100644 index 9e6810ab70cfa112fe066ec8b2122fbf76586317..0000000000000000000000000000000000000000 --- a/libs/iguanatls/test/github.pem +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j -ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 -LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug -RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm -+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW -PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM -xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB -Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 -hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg -EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA -FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec -nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z -eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF -hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 -Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe -vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep -+OkuE6N36B9K ------END CERTIFICATE----- diff --git a/libs/iguanatls/zig.mod b/libs/iguanatls/zig.mod deleted file mode 100644 index cb9d2b5ff1a5e213a7da5fb7453c83fefe932073..0000000000000000000000000000000000000000 --- a/libs/iguanatls/zig.mod +++ /dev/null @@ -1,4 +0,0 @@ -id: csbnipaad8n77buaszsnjvlmn6j173fl7pkprsctelswjywe -name: iguanatls -main: src/main.zig -dependencies: diff --git a/libs/zig-known-folders b/libs/zig-known-folders deleted file mode 160000 index e1193f9ef5b3aad7a6071e9f5721934fe04a020e..0000000000000000000000000000000000000000 --- a/libs/zig-known-folders +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e1193f9ef5b3aad7a6071e9f5721934fe04a020e diff --git a/libs/zuri/.github/workflows/ci.yml b/libs/zuri/.github/workflows/ci.yml deleted file mode 100644 index 728945e8748613a22348615f2d95d28cdd6db95d..0000000000000000000000000000000000000000 --- a/libs/zuri/.github/workflows/ci.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: CI -on: [push] -jobs: - test: - strategy: - matrix: - os: [ubuntu-latest] - runs-on: ${{matrix.os}} - steps: - - uses: actions/checkout@v2 - - uses: goto-bus-stop/setup-zig@v1.2.1 - with: - version: master - - run: zig build test - fmt: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: goto-bus-stop/setup-zig@v1.2.1 - with: - version: master - - run: zig fmt build.zig src --check \ No newline at end of file diff --git a/libs/zuri/.gitignore b/libs/zuri/.gitignore deleted file mode 100644 index dc8ac11325a786ee83397a0117d93d7619a2dff2..0000000000000000000000000000000000000000 --- a/libs/zuri/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.vscode/ -zig-cache/ \ No newline at end of file diff --git a/libs/zuri/LICENSE b/libs/zuri/LICENSE deleted file mode 100644 index 1d88d585a7b176cddeb10ac519229557364ef0a8..0000000000000000000000000000000000000000 --- a/libs/zuri/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Vexu - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/libs/zuri/README.md b/libs/zuri/README.md deleted file mode 100644 index 74583a064151c6e90cd35bf6e509ef4e2d652635..0000000000000000000000000000000000000000 --- a/libs/zuri/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# zuri -[URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) parser written in [Zig](https://ziglang.org/). - -## Example -```Zig -const uri = try Uri.parse("https://ziglang.org/documentation/master/#toc-Introduction"); -assert(mem.eql(u8, uri.scheme, "https")); -assert(mem.eql(u8, uri.host, "ziglang.org")); -assert(mem.eql(u8, uri.path, "/documentation/master/")); -assert(mem.eql(u8, uri.fragment, "toc-Introduction")); -``` - -## Zig version -By default the master branch version requires the latest Zig master version. - -For Zig 0.6.0 use commit 89ff8258ccd09d0a4cfda649828cc1c45f2b1f8f. \ No newline at end of file diff --git a/libs/zuri/build.zig b/libs/zuri/build.zig deleted file mode 100644 index 83076aacfe47bb87fe4b156fe82f0f7c23cfecae..0000000000000000000000000000000000000000 --- a/libs/zuri/build.zig +++ /dev/null @@ -1,10 +0,0 @@ -const Builder = @import("std").build.Builder; - -pub fn build(b: *Builder) void { - const tests = b.addTest("test.zig"); - tests.setBuildMode(b.standardReleaseOptions()); - - const test_step = b.step("test", "Run library tests"); - test_step.dependOn(&tests.step); - b.default_step.dependOn(test_step); -} diff --git a/libs/zuri/src/zuri.zig b/libs/zuri/src/zuri.zig deleted file mode 100644 index 6bac1dcd70c0328b59fe42f3046d3ec8b3f21ae5..0000000000000000000000000000000000000000 --- a/libs/zuri/src/zuri.zig +++ /dev/null @@ -1,592 +0,0 @@ -const std = @import("std"); -const mem = std.mem; -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; -const parseUnsigned = std.fmt.parseUnsigned; -const net = std.net; -const expect = std.testing.expect; - -const ValueMap = std.StringHashMap([]const u8); - -pub const Uri = struct { - scheme: []const u8, - username: []const u8, - password: []const u8, - host: Host, - port: ?u16, - path: []const u8, - query: []const u8, - fragment: []const u8, - len: usize, - - /// possible uri host values - pub const Host = union(enum) { - ip: net.Address, - name: []const u8, - }; - - /// possible errors for mapQuery - pub const MapError = error{ - NoQuery, - OutOfMemory, - }; - - /// map query string into a hashmap of key value pairs with no value being an empty string - pub fn mapQuery(allocator: *Allocator, query: []const u8) MapError!ValueMap { - if (query.len == 0) { - return error.NoQuery; - } - var map = ValueMap.init(allocator); - errdefer map.deinit(); - var start: u32 = 0; - var mid: u32 = 0; - for (query) |c, i| { - if (c == ';' or c == '&') { - if (mid != 0) { - _ = try map.put(query[start..mid], query[mid + 1 .. i]); - } else { - _ = try map.put(query[start..i], ""); - } - start = @truncate(u32, i + 1); - mid = 0; - } else if (c == '=') { - mid = @truncate(u32, i); - } - } - if (mid != 0) { - _ = try map.put(query[start..mid], query[mid + 1 ..]); - } else { - _ = try map.put(query[start..], ""); - } - - return map; - } - - /// possible errors for decode and encode - pub const EncodeError = error{ - InvalidCharacter, - OutOfMemory, - }; - - /// decode path if it is percent encoded - pub fn decode(allocator: *Allocator, path: []const u8) EncodeError!?[]u8 { - var ret: ?[]u8 = null; - var ret_index: usize = 0; - var i: usize = 0; - - while (i < path.len) : (i += 1) { - if (path[i] == '%') { - if (!isPchar(path[i..])) { - return error.InvalidCharacter; - } - if (ret == null) { - ret = try allocator.alloc(u8, path.len); - mem.copy(u8, ret.?, path[0..i]); - ret_index = i; - } - - // charToDigit can't fail because the chars are validated earlier - var new = (std.fmt.charToDigit(path[i + 1], 16) catch unreachable) << 4; - new |= std.fmt.charToDigit(path[i + 2], 16) catch unreachable; - ret.?[ret_index] = new; - ret_index += 1; - i += 2; - } else if (path[i] != '/' and !isPchar(path[i..])) { - return error.InvalidCharacter; - } else if (ret != null) { - ret.?[ret_index] = path[i]; - ret_index += 1; - } - } - if (ret != null) { - return allocator.realloc(ret.?, ret_index) catch ret.?[0..ret_index]; - } - return ret; - } - - /// percent encode if path contains characters not allowed in paths - pub fn encode(allocator: *Allocator, path: []const u8) EncodeError!?[]u8 { - var ret: ?[]u8 = null; - var ret_index: usize = 0; - for (path) |c, i| { - if (c != '/' and !isPchar(path[i..])) { - if (ret == null) { - ret = try allocator.alloc(u8, path.len * 3); - mem.copy(u8, ret.?, path[0..i]); - ret_index = i; - } - const hex_digits = "0123456789ABCDEF"; - ret.?[ret_index] = '%'; - ret.?[ret_index + 1] = hex_digits[(c & 0xF0) >> 4]; - ret.?[ret_index + 2] = hex_digits[c & 0x0F]; - ret_index += 3; - } else if (ret != null) { - ret.?[ret_index] = c; - ret_index += 1; - } - } - if (ret != null) { - return allocator.realloc(ret.?, ret_index) catch ret.?[0..ret_index]; - } - return ret; - } - - /// resolves `path`, leaves trailing '/' - /// assumes `path` to be valid - pub fn resolvePath(allocator: *Allocator, path: []const u8) error{OutOfMemory}![]u8 { - assert(path.len > 0); - var list = std.ArrayList([]const u8).init(allocator); - errdefer list.deinit(); - - var it = mem.tokenize(path, "/"); - while (it.next()) |p| { - if (mem.eql(u8, p, ".")) { - continue; - } else if (mem.eql(u8, p, "..")) { - _ = list.popOrNull(); - } else { - try list.append(p); - } - } - - var buf = try allocator.alloc(u8, path.len); - errdefer allocator.free(buf); - var len: usize = 0; - var segments = list.toOwnedSlice(); - defer allocator.free(segments); - - for (segments) |s| { - buf[len] = '/'; - len += 1; - mem.copy(u8, buf[len..], s); - len += s.len; - } - - if (path[path.len - 1] == '/') { - buf[len] = '/'; - len += 1; - } - - return allocator.realloc(buf, len) catch buf[0..len]; - } - - /// possible errors for parse - pub const Error = error{ - /// input is not a valid uri due to a invalid character - /// mostly a result of invalid ipv6 - InvalidCharacter, - - /// given input was empty - EmptyUri, - }; - - /// parse URI from input - /// empty input is an error - /// if assume_auth is true then `example.com` will result in `example.com` being the host instead of path - pub fn parse(input: []const u8, assume_auth: bool) Error!Uri { - if (input.len == 0) { - return error.EmptyUri; - } - var uri = Uri{ - .scheme = "", - .username = "", - .password = "", - .host = .{ .name = "" }, - .port = null, - .path = "", - .query = "", - .fragment = "", - .len = 0, - }; - - switch (input[0]) { - 'a'...'z', 'A'...'Z' => { - uri.parseMaybeScheme(input); - }, - else => {}, - } - - if (input.len > uri.len + 2 and input[uri.len] == '/' and input[uri.len + 1] == '/') { - uri.len += 2; // for the '//' - try uri.parseAuth(input[uri.len..]); - } else if (assume_auth) { - try uri.parseAuth(input[uri.len..]); - } - - // make host ip4 address if possible - if (uri.host == .name and uri.host.name.len > 0) blk: { - var a = net.Address.parseIp4(uri.host.name, 0) catch break :blk; - uri.host = .{ .ip = a }; // workaround for https://github.com/ziglang/zig/issues/3234 - } - - if (uri.host == .ip and uri.port != null) { - uri.host.ip.setPort(uri.port.?); - } - - uri.parsePath(input[uri.len..]); - - if (input.len > uri.len + 1 and input[uri.len] == '?') { - uri.parseQuery(input[uri.len + 1 ..]); - } - - if (input.len > uri.len + 1 and input[uri.len] == '#') { - uri.parseFragment(input[uri.len + 1 ..]); - } - return uri; - } - - fn parseMaybeScheme(u: *Uri, input: []const u8) void { - for (input) |c, i| { - switch (c) { - 'a'...'z', 'A'...'Z', '0'...'9', '+', '-', '.' => { - // allowed characters - }, - ':' => { - u.scheme = input[0..i]; - u.len += u.scheme.len + 1; // +1 for the ':' - return; - }, - else => { - // not a valid scheme - return; - }, - } - } - return; - } - - fn parseAuth(u: *Uri, input: []const u8) Error!void { - for (input) |c, i| { - switch (c) { - '@' => { - u.username = input[0..i]; - u.len += i + 1; // +1 for the '@' - return u.parseHost(input[i + 1 ..]); - }, - '[' => { - if (i != 0) - return error.InvalidCharacter; - return u.parseIP(input); - }, - ':' => { - u.host.name = input[0..i]; - u.len += i + 1; // +1 for the '@' - return u.parseAuthColon(input[i + 1 ..]); - }, - '/', '?', '#' => { - u.host.name = input[0..i]; - u.len += i; - return; - }, - else => if (!isPchar(input)) { - u.host.name = input[0..i]; - u.len += input.len; - return; - }, - } - } - u.host.name = input; - u.len += input.len; - } - - fn parseAuthColon(u: *Uri, input: []const u8) Error!void { - for (input) |c, i| { - if (c == '@') { - u.username = u.host.name; - u.password = input[0..i]; - u.len += i + 1; //1 for the '@' - return u.parseHost(input[i + 1 ..]); - } else if (c == '/' or c == '?' or c == '#' or !isPchar(input)) { - u.port = parseUnsigned(u16, input[0..i], 10) catch return error.InvalidCharacter; - u.len += i; - return; - } - } - u.port = parseUnsigned(u16, input, 10) catch return error.InvalidCharacter; - u.len += input.len; - } - - fn parseHost(u: *Uri, input: []const u8) Error!void { - for (input) |c, i| { - switch (c) { - ':' => { - u.host.name = input[0..i]; - u.len += i + 1; // +1 for the ':' - return u.parsePort(input[i..]); - }, - '[' => { - if (i != 0) - return error.InvalidCharacter; - return u.parseIP(input); - }, - else => if (c == '/' or c == '?' or c == '#' or !isPchar(input)) { - u.host.name = input[0..i]; - u.len += i; - return; - }, - } - } - u.host.name = input[0..]; - u.len += input.len; - } - - fn parseIP(u: *Uri, input: []const u8) Error!void { - const end = mem.indexOfScalar(u8, input, ']') orelse return error.InvalidCharacter; - var addr = net.Address.parseIp6(input[1..end], 0) catch return error.InvalidCharacter; - u.host = .{ .ip = addr }; - u.len += end + 1; - - if (input.len > end + 2 and input[end + 1] == ':') { - u.len += 1; - try u.parsePort(input[end + 2 ..]); - } - } - - fn parsePort(u: *Uri, input: []const u8) Error!void { - for (input) |c, i| { - switch (c) { - '0'...'9' => { - // digits - }, - else => { - if (i == 0) return error.InvalidCharacter; - u.port = parseUnsigned(u16, input[0..i], 10) catch return error.InvalidCharacter; - u.len += i; - return; - }, - } - } - if (input.len == 0) return error.InvalidCharacter; - u.port = parseUnsigned(u16, input[0..], 10) catch return error.InvalidCharacter; - u.len += input.len; - } - - fn parsePath(u: *Uri, input: []const u8) void { - for (input) |c, i| { - if (c != '/' and (c == '?' or c == '#' or !isPchar(input[i..]))) { - u.path = input[0..i]; - u.len += u.path.len; - return; - } - } - u.path = input[0..]; - u.len += u.path.len; - } - - fn parseQuery(u: *Uri, input: []const u8) void { - u.len += 1; // +1 for the '?' - for (input) |c, i| { - if (c == '#' or (c != '/' and c != '?' and !isPchar(input[i..]))) { - u.query = input[0..i]; - u.len += u.query.len; - return; - } - } - u.query = input; - u.len += input.len; - } - - fn parseFragment(u: *Uri, input: []const u8) void { - u.len += 1; // +1 for the '#' - for (input) |c, i| { - if (c != '/' and c != '?' and !isPchar(input[i..])) { - u.fragment = input[0..i]; - u.len += u.fragment.len; - return; - } - } - u.fragment = input; - u.len += u.fragment.len; - } - - /// returns true if str starts with a valid path character or a percent encoded octet - pub fn isPchar(str: []const u8) bool { - assert(str.len > 0); - return switch (str[0]) { - 'a'...'z', 'A'...'Z', '0'...'9', '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@' => true, - '%' => str.len > 3 and isHex(str[1]) and isHex(str[2]), - else => false, - }; - } - - /// returns true if c is a hexadecimal digit - pub fn isHex(c: u8) bool { - return switch (c) { - '0'...'9', 'a'...'f', 'A'...'F' => true, - else => false, - }; - } -}; - -test "basic url" { - const uri = try Uri.parse("https://ziglang.org:80/documentation/master/?test#toc-Introduction", false); - expect(mem.eql(u8, uri.scheme, "https")); - expect(mem.eql(u8, uri.username, "")); - expect(mem.eql(u8, uri.password, "")); - expect(mem.eql(u8, uri.host.name, "ziglang.org")); - expect(uri.port.? == 80); - expect(mem.eql(u8, uri.path, "/documentation/master/")); - expect(mem.eql(u8, uri.query, "test")); - expect(mem.eql(u8, uri.fragment, "toc-Introduction")); - expect(uri.len == 66); -} - -test "short" { - const uri = try Uri.parse("telnet://192.0.2.16:80/", false); - expect(mem.eql(u8, uri.scheme, "telnet")); - expect(mem.eql(u8, uri.username, "")); - expect(mem.eql(u8, uri.password, "")); - var buf = [_]u8{0} ** 100; - var ip = std.fmt.bufPrint(buf[0..], "{}", .{uri.host.ip}) catch unreachable; - expect(mem.eql(u8, ip, "192.0.2.16:80")); - expect(uri.port.? == 80); - expect(mem.eql(u8, uri.path, "/")); - expect(mem.eql(u8, uri.query, "")); - expect(mem.eql(u8, uri.fragment, "")); - expect(uri.len == 23); -} - -test "single char" { - const uri = try Uri.parse("a", false); - expect(mem.eql(u8, uri.scheme, "")); - expect(mem.eql(u8, uri.username, "")); - expect(mem.eql(u8, uri.password, "")); - expect(mem.eql(u8, uri.host.name, "")); - expect(uri.port == null); - expect(mem.eql(u8, uri.path, "a")); - expect(mem.eql(u8, uri.query, "")); - expect(mem.eql(u8, uri.fragment, "")); - expect(uri.len == 1); -} - -test "ipv6" { - const uri = try Uri.parse("ldap://[2001:db8::7]/c=GB?objectClass?one", false); - expect(mem.eql(u8, uri.scheme, "ldap")); - expect(mem.eql(u8, uri.username, "")); - expect(mem.eql(u8, uri.password, "")); - var buf = [_]u8{0} ** 100; - var ip = std.fmt.bufPrint(buf[0..], "{}", .{uri.host.ip}) catch unreachable; - expect(std.mem.eql(u8, ip, "[2001:db8::7]:0")); - expect(uri.port == null); - expect(mem.eql(u8, uri.path, "/c=GB")); - expect(mem.eql(u8, uri.query, "objectClass?one")); - expect(mem.eql(u8, uri.fragment, "")); - expect(uri.len == 41); -} - -test "mailto" { - const uri = try Uri.parse("mailto:John.Doe@example.com", false); - expect(mem.eql(u8, uri.scheme, "mailto")); - expect(mem.eql(u8, uri.username, "")); - expect(mem.eql(u8, uri.password, "")); - expect(mem.eql(u8, uri.host.name, "")); - expect(uri.port == null); - expect(mem.eql(u8, uri.path, "John.Doe@example.com")); - expect(mem.eql(u8, uri.query, "")); - expect(mem.eql(u8, uri.fragment, "")); - expect(uri.len == 27); -} - -test "tel" { - const uri = try Uri.parse("tel:+1-816-555-1212", false); - expect(mem.eql(u8, uri.scheme, "tel")); - expect(mem.eql(u8, uri.username, "")); - expect(mem.eql(u8, uri.password, "")); - expect(mem.eql(u8, uri.host.name, "")); - expect(uri.port == null); - expect(mem.eql(u8, uri.path, "+1-816-555-1212")); - expect(mem.eql(u8, uri.query, "")); - expect(mem.eql(u8, uri.fragment, "")); - expect(uri.len == 19); -} - -test "urn" { - const uri = try Uri.parse("urn:oasis:names:specification:docbook:dtd:xml:4.1.2", false); - expect(mem.eql(u8, uri.scheme, "urn")); - expect(mem.eql(u8, uri.username, "")); - expect(mem.eql(u8, uri.password, "")); - expect(mem.eql(u8, uri.host.name, "")); - expect(uri.port == null); - expect(mem.eql(u8, uri.path, "oasis:names:specification:docbook:dtd:xml:4.1.2")); - expect(mem.eql(u8, uri.query, "")); - expect(mem.eql(u8, uri.fragment, "")); - expect(uri.len == 51); -} - -test "userinfo" { - const uri = try Uri.parse("ftp://username:password@host.com/", false); - expect(mem.eql(u8, uri.scheme, "ftp")); - expect(mem.eql(u8, uri.username, "username")); - expect(mem.eql(u8, uri.password, "password")); - expect(mem.eql(u8, uri.host.name, "host.com")); - expect(uri.port == null); - expect(mem.eql(u8, uri.path, "/")); - expect(mem.eql(u8, uri.query, "")); - expect(mem.eql(u8, uri.fragment, "")); - expect(uri.len == 33); -} - -test "map query" { - const uri = try Uri.parse("https://ziglang.org:80/documentation/master/?test;1=true&false#toc-Introduction", false); - expect(mem.eql(u8, uri.scheme, "https")); - expect(mem.eql(u8, uri.username, "")); - expect(mem.eql(u8, uri.password, "")); - expect(mem.eql(u8, uri.host.name, "ziglang.org")); - expect(uri.port.? == 80); - expect(mem.eql(u8, uri.path, "/documentation/master/")); - expect(mem.eql(u8, uri.query, "test;1=true&false")); - expect(mem.eql(u8, uri.fragment, "toc-Introduction")); - var map = try Uri.mapQuery(alloc, uri.query); - defer map.deinit(); - expect(mem.eql(u8, map.get("test").?, "")); - expect(mem.eql(u8, map.get("1").?, "true")); - expect(mem.eql(u8, map.get("false").?, "")); -} - -test "ends in space" { - const uri = try Uri.parse("https://ziglang.org/documentation/master/ something else", false); - expect(mem.eql(u8, uri.scheme, "https")); - expect(mem.eql(u8, uri.username, "")); - expect(mem.eql(u8, uri.password, "")); - expect(mem.eql(u8, uri.host.name, "ziglang.org")); - expect(mem.eql(u8, uri.path, "/documentation/master/")); - expect(uri.len == 41); -} - -test "assume auth" { - const uri = try Uri.parse("ziglang.org", true); - expect(mem.eql(u8, uri.host.name, "ziglang.org")); - expect(uri.len == 11); -} - -var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); -const alloc = &arena.allocator; - -test "encode" { - const path = (try Uri.encode(alloc, "/안녕하세요.html")).?; - expect(mem.eql(u8, path, "/%EC%95%88%EB%85%95%ED%95%98%EC%84%B8%EC%9A%94.html")); -} - -test "decode" { - const path = (try Uri.decode(alloc, "/%EC%95%88%EB%85%95%ED%95%98%EC%84%B8%EC%9A%94.html")).?; - expect(mem.eql(u8, path, "/안녕하세요.html")); -} - -test "resolvePath" { - var a = try Uri.resolvePath(alloc, "/a/b/.."); - expect(mem.eql(u8, a, "/a")); - a = try Uri.resolvePath(alloc, "/a/b/../"); - expect(mem.eql(u8, a, "/a/")); - a = try Uri.resolvePath(alloc, "/a/b/c/../d/../"); - expect(mem.eql(u8, a, "/a/b/")); - a = try Uri.resolvePath(alloc, "/a/b/c/../d/.."); - expect(mem.eql(u8, a, "/a/b")); - a = try Uri.resolvePath(alloc, "/a/b/c/../d/.././"); - expect(mem.eql(u8, a, "/a/b/")); - a = try Uri.resolvePath(alloc, "/a/b/c/../d/../."); - expect(mem.eql(u8, a, "/a/b")); - a = try Uri.resolvePath(alloc, "/a/../../"); - expect(mem.eql(u8, a, "/")); - - arena.deinit(); -} diff --git a/libs/zuri/test.zig b/libs/zuri/test.zig deleted file mode 100644 index bdcdeda8aac9519d4dd952abc11a3e48daee7d95..0000000000000000000000000000000000000000 --- a/libs/zuri/test.zig +++ /dev/null @@ -1,3 +0,0 @@ -comptime { - _ = @import("src/zuri.zig"); -} -- 2.54.0