authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2021-02-20 19:08:28 -08:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2021-02-20 19:08:28 -08:00
log0da1bebc31daf70296b708161d6e46bb3567ce9b
tree221ff65176e8839bc668be0ccdc96c110146a766
parent1ea33bafa65492da2de36b8e1c93a430c4c484c3

remove unneeded git submodules


27 files changed, 0 insertions(+), 6286 deletions(-)

libs/iguanatls/.gitattributes deleted-1
......@@ -1 +0,0 @@
1*.zig text=auto eol=lf
libs/iguanatls/.gitignore deleted-3
......@@ -1,3 +0,0 @@
1/zig-cache
2deps.zig
3gyro.lock
libs/iguanatls/.gitmodules
libs/iguanatls/LICENSE deleted-21
......@@ -1,21 +0,0 @@
1MIT License
2
3Copyright (c) 2020 Alexandros Naskos
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in all
13copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21SOFTWARE.
libs/iguanatls/bench/bench.zig deleted-425
......@@ -1,425 +0,0 @@
1const std = @import("std");
2const tls = @import("tls");
3const use_gpa = @import("build_options").use_gpa;
4
5pub const log_level = .debug;
6
7const RecordingAllocator = struct {
8 const Stats = struct {
9 peak_allocated: usize = 0,
10 total_allocated: usize = 0,
11 total_deallocated: usize = 0,
12 total_allocations: usize = 0,
13 };
14
15 allocator: std.mem.Allocator = .{
16 .allocFn = allocFn,
17 .resizeFn = resizeFn,
18 },
19 base_allocator: *std.mem.Allocator,
20 stats: Stats = .{},
21
22 fn allocFn(
23 a: *std.mem.Allocator,
24 len: usize,
25 ptr_align: u29,
26 len_align: u29,
27 ret_addr: usize,
28 ) ![]u8 {
29 const self = @fieldParentPtr(RecordingAllocator, "allocator", a);
30 const mem = try self.base_allocator.allocFn(
31 self.base_allocator,
32 len,
33 ptr_align,
34 len_align,
35 ret_addr,
36 );
37
38 self.stats.total_allocations += 1;
39 self.stats.total_allocated += mem.len;
40 self.stats.peak_allocated = std.math.max(
41 self.stats.peak_allocated,
42 self.stats.total_allocated - self.stats.total_deallocated,
43 );
44 return mem;
45 }
46
47 fn resizeFn(a: *std.mem.Allocator, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) !usize {
48 const self = @fieldParentPtr(RecordingAllocator, "allocator", a);
49 const actual_len = try self.base_allocator.resizeFn(
50 self.base_allocator,
51 buf,
52 buf_align,
53 new_len,
54 len_align,
55 ret_addr,
56 );
57
58 if (actual_len == 0) {
59 std.debug.assert(new_len == 0);
60 self.stats.total_deallocated += buf.len;
61 } else if (actual_len > buf.len) {
62 self.stats.total_allocated += actual_len - buf.len;
63 self.stats.peak_allocated = std.math.max(
64 self.stats.peak_allocated,
65 self.stats.total_allocated - self.stats.total_deallocated,
66 );
67 } else {
68 self.stats.total_deallocated += buf.len - actual_len;
69 }
70 return actual_len;
71 }
72};
73
74const SinkWriter = blk: {
75 const S = struct {};
76 break :blk std.io.Writer(S, error{}, struct {
77 fn f(_: S, buffer: []const u8) !usize {
78 return buffer.len;
79 }
80 }.f);
81};
82
83const ReplayingReaderState = struct {
84 data: []const u8,
85};
86const ReplayingReader = std.io.Reader(*ReplayingReaderState, error{}, struct {
87 fn f(self: *ReplayingReaderState, buffer: []u8) !usize {
88 if (self.data.len < buffer.len)
89 @panic("Not enoguh reader data!");
90 std.mem.copy(u8, buffer, self.data[0..buffer.len]);
91 self.data = self.data[buffer.len..];
92 return buffer.len;
93 }
94}.f);
95
96const ReplayingRandom = struct {
97 rand: std.rand.Random = .{ .fillFn = fillFn },
98 data: []const u8,
99
100 fn fillFn(r: *std.rand.Random, buf: []u8) void {
101 const self = @fieldParentPtr(ReplayingRandom, "rand", r);
102 if (self.data.len < buf.len)
103 @panic("Not enough random data!");
104 std.mem.copy(u8, buf, self.data[0..buf.len]);
105 self.data = self.data[buf.len..];
106 }
107};
108
109fn benchmark_run(
110 comptime ciphersuites: anytype,
111 comptime curves: anytype,
112 gpa: *std.mem.Allocator,
113 allocator: *std.mem.Allocator,
114 running_time: f32,
115 hostname: []const u8,
116 port: u16,
117 trust_anchors: tls.x509.TrustAnchorChain,
118 reader_recording: []const u8,
119 random_recording: []const u8,
120) !void {
121 {
122 const warmup_time_secs = std.math.max(0.5, running_time / 20);
123 std.debug.print("Warming up for {d:.2} seconds...\n", .{warmup_time_secs});
124 const warmup_time_ns = @floatToInt(i128, warmup_time_secs * std.time.ns_per_s);
125
126 var warmup_time_passed: i128 = 0;
127 var timer = try std.time.Timer.start();
128 while (warmup_time_passed < warmup_time_ns) {
129 var rand = ReplayingRandom{
130 .data = random_recording,
131 };
132 var reader_state = ReplayingReaderState{
133 .data = reader_recording,
134 };
135 const reader = ReplayingReader{ .context = &reader_state };
136 const writer = SinkWriter{ .context = .{} };
137
138 timer.reset();
139 _ = try tls.client_connect(.{
140 .rand = &rand.rand,
141 .reader = reader,
142 .writer = writer,
143 .ciphersuites = ciphersuites,
144 .curves = curves,
145 .cert_verifier = .default,
146 .temp_allocator = allocator,
147 .trusted_certificates = trust_anchors.data.items,
148 }, hostname);
149 warmup_time_passed += timer.read();
150 }
151 }
152 {
153 std.debug.print("Benchmarking for {d:.2} seconds...\n", .{running_time});
154
155 const RunRecording = struct {
156 time: i128,
157 mem_stats: RecordingAllocator.Stats,
158 };
159 var run_recordings = std.ArrayList(RunRecording).init(gpa);
160
161 defer run_recordings.deinit();
162 const bench_time_ns = @floatToInt(i128, running_time * std.time.ns_per_s);
163
164 var total_time_passed: i128 = 0;
165 var iterations: usize = 0;
166 var timer = try std.time.Timer.start();
167 while (total_time_passed < bench_time_ns) : (iterations += 1) {
168 var rand = ReplayingRandom{
169 .data = random_recording,
170 };
171 var reader_state = ReplayingReaderState{
172 .data = reader_recording,
173 };
174 const reader = ReplayingReader{ .context = &reader_state };
175 const writer = SinkWriter{ .context = .{} };
176 var recording_allocator = RecordingAllocator{ .base_allocator = allocator };
177
178 timer.reset();
179 _ = try tls.client_connect(.{
180 .rand = &rand.rand,
181 .reader = reader,
182 .writer = writer,
183 .ciphersuites = ciphersuites,
184 .curves = curves,
185 .cert_verifier = .default,
186 .temp_allocator = &recording_allocator.allocator,
187 .trusted_certificates = trust_anchors.data.items,
188 }, hostname);
189 const runtime = timer.read();
190 total_time_passed += runtime;
191
192 (try run_recordings.addOne()).* = .{
193 .mem_stats = recording_allocator.stats,
194 .time = runtime,
195 };
196 }
197
198 const total_time_secs = @intToFloat(f64, total_time_passed) / std.time.ns_per_s;
199 const mean_time_ns = @divTrunc(total_time_passed, iterations);
200 const mean_time_ms = @intToFloat(f64, mean_time_ns) * std.time.ms_per_s / std.time.ns_per_s;
201
202 const std_dev_ns = blk: {
203 var acc: i128 = 0;
204 for (run_recordings.items) |rec| {
205 const dt = rec.time - mean_time_ns;
206 acc += dt * dt;
207 }
208 break :blk std.math.sqrt(@divTrunc(acc, iterations));
209 };
210 const std_dev_ms = @intToFloat(f64, std_dev_ns) * std.time.ms_per_s / std.time.ns_per_s;
211
212 std.debug.print(
213 \\Finished benchmarking.
214 \\Total runtime: {d:.2} sec
215 \\Iterations: {} ({d:.2} iterations/sec)
216 \\Mean iteration time: {d:.2} ms
217 \\Standard deviation: {d:.2} ms
218 \\
219 , .{
220 total_time_secs,
221 iterations,
222 @intToFloat(f64, iterations) / total_time_secs,
223 mean_time_ms,
224 std_dev_ms,
225 });
226
227 // (percentile/100) * (total number n + 1)
228 std.sort.sort(RunRecording, run_recordings.items, {}, struct {
229 fn f(_: void, lhs: RunRecording, rhs: RunRecording) bool {
230 return lhs.time < rhs.time;
231 }
232 }.f);
233 const percentiles = .{ 99.0, 90.0, 75.0, 50.0 };
234 inline for (percentiles) |percentile| {
235 if (percentile < iterations) {
236 const idx = @floatToInt(usize, @intToFloat(f64, iterations + 1) * percentile / 100.0);
237 std.debug.print(
238 "{d:.0}th percentile value: {d:.2} ms\n",
239 .{
240 percentile,
241 @intToFloat(f64, run_recordings.items[idx].time) * std.time.ms_per_s / std.time.ns_per_s,
242 },
243 );
244 }
245 }
246
247 const first_mem_stats = run_recordings.items[0].mem_stats;
248 for (run_recordings.items[1..]) |rec| {
249 std.debug.assert(std.meta.eql(first_mem_stats, rec.mem_stats));
250 }
251
252 std.debug.print(
253 \\Peak allocated memory: {Bi:.2},
254 \\Total allocated memory: {Bi:.2},
255 \\Number of allocations: {d},
256 \\
257 , .{
258 first_mem_stats.peak_allocated,
259 first_mem_stats.total_allocated,
260 first_mem_stats.total_allocations,
261 });
262 }
263}
264
265fn benchmark_run_with_ciphersuite(
266 comptime ciphersuites: anytype,
267 curve_str: []const u8,
268 gpa: *std.mem.Allocator,
269 allocator: *std.mem.Allocator,
270 running_time: f32,
271 hostname: []const u8,
272 port: u16,
273 trust_anchors: tls.x509.TrustAnchorChain,
274 reader_recording: []const u8,
275 random_recording: []const u8,
276) !void {
277 if (std.mem.eql(u8, curve_str, "all")) {
278 return try benchmark_run(
279 ciphersuites,
280 tls.curves.all,
281 gpa,
282 allocator,
283 running_time,
284 hostname,
285 port,
286 trust_anchors,
287 reader_recording,
288 random_recording,
289 );
290 }
291 inline for (tls.curves.all) |curve| {
292 if (std.mem.eql(u8, curve_str, curve.name)) {
293 return try benchmark_run(
294 ciphersuites,
295 .{curve},
296 gpa,
297 allocator,
298 running_time,
299 hostname,
300 port,
301 trust_anchors,
302 reader_recording,
303 random_recording,
304 );
305 }
306 }
307 return error.InvalidCurve;
308}
309
310pub fn main() !void {
311 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
312 const allocator = &gpa.allocator;
313
314 var args = std.process.args();
315 std.debug.assert(args.skip());
316
317 const running_time = blk: {
318 const maybe_arg = args.next(allocator) orelse return error.NoArguments;
319 const arg = try maybe_arg;
320 break :blk std.fmt.parseFloat(f32, arg) catch {
321 std.log.crit("Running time is not a floating point number...", .{});
322 return error.InvalidArg;
323 };
324 };
325
326 // Loop over all files, swap gpa with a fixed buffer allocator for the handhsake
327 arg_loop: while (args.next(allocator)) |recorded_file_path_or_err| {
328 const recorded_file_path = try recorded_file_path_or_err;
329 defer allocator.free(recorded_file_path);
330
331 std.debug.print(
332 \\============================================================
333 \\{s}
334 \\============================================================
335 \\
336 , .{std.fs.path.basename(recorded_file_path)});
337
338 const recorded_file = try std.fs.cwd().openFile(recorded_file_path, .{});
339 defer recorded_file.close();
340
341 const ciphersuite_str_len = try recorded_file.reader().readByte();
342 const ciphersuite_str = try allocator.alloc(u8, ciphersuite_str_len);
343 defer allocator.free(ciphersuite_str);
344 try recorded_file.reader().readNoEof(ciphersuite_str);
345
346 const curve_str_len = try recorded_file.reader().readByte();
347 const curve_str = try allocator.alloc(u8, curve_str_len);
348 defer allocator.free(curve_str);
349 try recorded_file.reader().readNoEof(curve_str);
350
351 const hostname_len = try recorded_file.reader().readIntLittle(usize);
352 const hostname = try allocator.alloc(u8, hostname_len);
353 defer allocator.free(hostname);
354 try recorded_file.reader().readNoEof(hostname);
355
356 const port = try recorded_file.reader().readIntLittle(u16);
357
358 const trust_anchors = blk: {
359 const pem_file_path_len = try recorded_file.reader().readIntLittle(usize);
360 const pem_file_path = try allocator.alloc(u8, pem_file_path_len);
361 defer allocator.free(pem_file_path);
362 try recorded_file.reader().readNoEof(pem_file_path);
363
364 const pem_file = try std.fs.cwd().openFile(pem_file_path, .{});
365 defer pem_file.close();
366
367 const tas = try tls.x509.TrustAnchorChain.from_pem(allocator, pem_file.reader());
368 std.debug.print("Read {} certificates.\n", .{tas.data.items.len});
369 break :blk tas;
370 };
371 defer trust_anchors.deinit();
372
373 const reader_recording_len = try recorded_file.reader().readIntLittle(usize);
374 const reader_recording = try allocator.alloc(u8, reader_recording_len);
375 defer allocator.free(reader_recording);
376 try recorded_file.reader().readNoEof(reader_recording);
377
378 const random_recording_len = try recorded_file.reader().readIntLittle(usize);
379 const random_recording = try allocator.alloc(u8, random_recording_len);
380 defer allocator.free(random_recording);
381 try recorded_file.reader().readNoEof(random_recording);
382
383 const handshake_allocator = if (use_gpa)
384 &gpa.allocator
385 else
386 &std.heap.ArenaAllocator.init(std.heap.page_allocator).allocator;
387
388 defer if (!use_gpa)
389 @fieldParentPtr(std.heap.ArenaAllocator, "allocator", handshake_allocator).deinit();
390
391 if (std.mem.eql(u8, ciphersuite_str, "all")) {
392 try benchmark_run_with_ciphersuite(
393 tls.ciphersuites.all,
394 curve_str,
395 allocator,
396 handshake_allocator,
397 running_time,
398 hostname,
399 port,
400 trust_anchors,
401 reader_recording,
402 random_recording,
403 );
404 continue :arg_loop;
405 }
406 inline for (tls.ciphersuites.all) |ciphersuite| {
407 if (std.mem.eql(u8, ciphersuite_str, ciphersuite.name)) {
408 try benchmark_run_with_ciphersuite(
409 .{ciphersuite},
410 curve_str,
411 allocator,
412 handshake_allocator,
413 running_time,
414 hostname,
415 port,
416 trust_anchors,
417 reader_recording,
418 random_recording,
419 );
420 continue :arg_loop;
421 }
422 }
423 return error.InvalidCiphersuite;
424 }
425}
libs/iguanatls/bench/build.zig deleted-33
......@@ -1,33 +0,0 @@
1const Builder = @import("std").build.Builder;
2
3pub fn build(b: *Builder) void {
4 const record_build = b.addExecutable("record_handshake", "record_handshake.zig");
5 record_build.addPackagePath("tls", "../src/main.zig");
6 record_build.setBuildMode(.Debug);
7 record_build.install();
8
9 const use_gpa = b.option(
10 bool,
11 "use-gpa",
12 "Use the general purpose allocator instead of an arena allocator",
13 ) orelse false;
14 const bench_build = b.addExecutable("bench", "bench.zig");
15 bench_build.addPackagePath("tls", "../src/main.zig");
16 bench_build.setBuildMode(.ReleaseFast);
17 bench_build.addBuildOption(bool, "use_gpa", use_gpa);
18 bench_build.install();
19
20 const record_run_cmd = record_build.run();
21 const bench_run_cmd = bench_build.run();
22 record_run_cmd.step.dependOn(b.getInstallStep());
23 bench_run_cmd.step.dependOn(b.getInstallStep());
24 if (b.args) |args| {
25 record_run_cmd.addArgs(args);
26 bench_run_cmd.addArgs(args);
27 }
28
29 const record_run_step = b.step("record-handshake", "Record a TLS handshake");
30 const bench_run_step = b.step("bench", "Run the benchmark");
31 record_run_step.dependOn(&record_run_cmd.step);
32 bench_run_step.dependOn(&bench_run_cmd.step);
33}
libs/iguanatls/bench/record_handshake.zig deleted-249
......@@ -1,249 +0,0 @@
1const std = @import("std");
2const tls = @import("tls");
3
4const RecordingRandom = struct {
5 rand: std.rand.Random = .{
6 .fillFn = fillFn,
7 },
8 base: *std.rand.Random,
9 recorded: std.ArrayList(u8),
10
11 fn fillFn(r: *std.rand.Random, buf: []u8) void {
12 const self = @fieldParentPtr(@This(), "rand", r);
13 self.base.bytes(buf);
14 self.recorded.writer().writeAll(buf) catch unreachable;
15 }
16};
17
18fn RecordingReaderState(comptime Base: type) type {
19 return struct {
20 base: Base,
21 recorded: std.ArrayList(u8),
22
23 fn read(self: *@This(), buffer: []u8) !usize {
24 var read_bytes = try self.base.read(buffer);
25 if (read_bytes != 0) {
26 try self.recorded.writer().writeAll(buffer[0..read_bytes]);
27 }
28 return read_bytes;
29 }
30 };
31}
32
33fn RecordingReader(comptime Base: type) type {
34 return std.io.Reader(
35 *RecordingReaderState(Base),
36 Base.Error || error{OutOfMemory},
37 RecordingReaderState(Base).read,
38 );
39}
40
41fn record_handshake(
42 comptime ciphersuites: anytype,
43 comptime curves: anytype,
44 allocator: *std.mem.Allocator,
45 out_name: []const u8,
46 hostname: []const u8,
47 port: u16,
48 pem_file_path: []const u8,
49) !void {
50 // Read PEM file
51 const pem_file = try std.fs.cwd().openFile(pem_file_path, .{});
52 defer pem_file.close();
53
54 const trust_anchors = try tls.x509.TrustAnchorChain.from_pem(allocator, pem_file.reader());
55 defer trust_anchors.deinit();
56 std.log.info("Read {} certificates.", .{trust_anchors.data.items.len});
57
58 const sock = try std.net.tcpConnectToHost(allocator, hostname, port);
59 defer sock.close();
60
61 var recording_reader_state = RecordingReaderState(@TypeOf(sock).Reader){
62 .base = sock.reader(),
63 .recorded = std.ArrayList(u8).init(allocator),
64 };
65 defer recording_reader_state.recorded.deinit();
66
67 var recording_random = RecordingRandom{
68 .base = std.crypto.random,
69 .recorded = std.ArrayList(u8).init(allocator),
70 };
71 defer recording_random.recorded.deinit();
72
73 const reader = RecordingReader(@TypeOf(sock).Reader){
74 .context = &recording_reader_state,
75 };
76 std.log.info("Recording session `{s}`...", .{out_name});
77 var client = try tls.client_connect(.{
78 .rand = &recording_random.rand,
79 .reader = reader,
80 .writer = sock.writer(),
81 .ciphersuites = ciphersuites,
82 .curves = curves,
83 .cert_verifier = .default,
84 .temp_allocator = allocator,
85 .trusted_certificates = trust_anchors.data.items,
86 }, hostname);
87 defer client.close_notify() catch {};
88
89 const out_file = try std.fs.cwd().createFile(out_name, .{});
90 defer out_file.close();
91
92 if (ciphersuites.len > 1) {
93 try out_file.writeAll(&[_]u8{ 0x3, 'a', 'l', 'l' });
94 } else {
95 try out_file.writer().writeIntLittle(u8, ciphersuites[0].name.len);
96 try out_file.writeAll(ciphersuites[0].name);
97 }
98 if (curves.len > 1) {
99 try out_file.writeAll(&[_]u8{ 0x3, 'a', 'l', 'l' });
100 } else {
101 try out_file.writer().writeIntLittle(u8, curves[0].name.len);
102 try out_file.writeAll(curves[0].name);
103 }
104 try out_file.writer().writeIntLittle(usize, hostname.len);
105 try out_file.writeAll(hostname);
106 try out_file.writer().writeIntLittle(u16, port);
107 try out_file.writer().writeIntLittle(usize, pem_file_path.len);
108 try out_file.writeAll(pem_file_path);
109 try out_file.writer().writeIntLittle(usize, recording_reader_state.recorded.items.len);
110 try out_file.writeAll(recording_reader_state.recorded.items);
111 try out_file.writer().writeIntLittle(usize, recording_random.recorded.items.len);
112 try out_file.writeAll(recording_random.recorded.items);
113 std.log.info("Session recorded.\n", .{});
114}
115
116fn record_with_ciphersuite(
117 comptime ciphersuites: anytype,
118 allocator: *std.mem.Allocator,
119 out_name: []const u8,
120 curve_str: []const u8,
121 hostname: []const u8,
122 port: u16,
123 pem_file_path: []const u8,
124) !void {
125 if (std.mem.eql(u8, curve_str, "all")) {
126 return try record_handshake(
127 ciphersuites,
128 tls.curves.all,
129 allocator,
130 out_name,
131 hostname,
132 port,
133 pem_file_path,
134 );
135 }
136 inline for (tls.curves.all) |curve| {
137 if (std.mem.eql(u8, curve_str, curve.name)) {
138 return try record_handshake(
139 ciphersuites,
140 .{curve},
141 allocator,
142 out_name,
143 hostname,
144 port,
145 pem_file_path,
146 );
147 }
148 }
149 std.log.crit("Invalid curve `{s}`", .{curve_str});
150 std.debug.warn("Available options:\n- all\n", .{});
151 inline for (tls.curves.all) |curve| {
152 std.debug.warn("- {s}\n", .{curve.name});
153 }
154 return error.InvalidArg;
155}
156
157var gpa = std.heap.GeneralPurposeAllocator(.{}){};
158pub fn main() !void {
159 const allocator = &gpa.allocator;
160
161 var args = std.process.args();
162 std.debug.assert(args.skip());
163
164 const pem_file_path = try (args.next(allocator) orelse {
165 std.log.crit("Need PEM file path as first argument", .{});
166 return error.NotEnoughArgs;
167 });
168 defer allocator.free(pem_file_path);
169
170 const ciphersuite_str = try (args.next(allocator) orelse {
171 std.log.crit("Need ciphersuite as second argument", .{});
172 return error.NotEnoughArgs;
173 });
174 defer allocator.free(ciphersuite_str);
175
176 const curve_str = try (args.next(allocator) orelse {
177 std.log.crit("Need curve as third argument", .{});
178 return error.NotEnoughArgs;
179 });
180 defer allocator.free(curve_str);
181
182 const hostname_port = try (args.next(allocator) orelse {
183 std.log.crit("Need hostname:port as fourth argument", .{});
184 return error.NotEnoughArgs;
185 });
186 defer allocator.free(hostname_port);
187
188 if (args.skip()) {
189 std.log.crit("Need exactly four arguments", .{});
190 return error.TooManyArgs;
191 }
192
193 var hostname_parts = std.mem.split(hostname_port, ":");
194 const hostname = hostname_parts.next().?;
195 const port = std.fmt.parseUnsigned(
196 u16,
197 hostname_parts.next() orelse {
198 std.log.crit("Hostname and port should be in `hostname:port` format", .{});
199 return error.InvalidArg;
200 },
201 10,
202 ) catch {
203 std.log.crit("Port is not a base 10 unsigned integer...", .{});
204 return error.InvalidArg;
205 };
206 if (hostname_parts.next() != null) {
207 std.log.crit("Hostname and port should be in `hostname:port` format", .{});
208 return error.InvalidArg;
209 }
210
211 const out_name = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}-{}.handshake", .{
212 hostname,
213 ciphersuite_str,
214 curve_str,
215 std.time.timestamp(),
216 });
217 defer allocator.free(out_name);
218
219 if (std.mem.eql(u8, ciphersuite_str, "all")) {
220 return try record_with_ciphersuite(
221 tls.ciphersuites.all,
222 allocator,
223 out_name,
224 curve_str,
225 hostname,
226 port,
227 pem_file_path,
228 );
229 }
230 inline for (tls.ciphersuites.all) |ciphersuite| {
231 if (std.mem.eql(u8, ciphersuite_str, ciphersuite.name)) {
232 return try record_with_ciphersuite(
233 .{ciphersuite},
234 allocator,
235 out_name,
236 curve_str,
237 hostname,
238 port,
239 pem_file_path,
240 );
241 }
242 }
243 std.log.crit("Invalid ciphersuite `{s}`", .{ciphersuite_str});
244 std.debug.warn("Available options:\n- all\n", .{});
245 inline for (tls.ciphersuites.all) |ciphersuite| {
246 std.debug.warn("- {s}\n", .{ciphersuite.name});
247 }
248 return error.InvalidArg;
249}
libs/iguanatls/build.zig deleted-14
......@@ -1,14 +0,0 @@
1const Builder = @import("std").build.Builder;
2
3pub fn build(b: *Builder) void {
4 const mode = b.standardReleaseOptions();
5 const lib = b.addStaticLibrary("iguanaTLS", "src/main.zig");
6 lib.setBuildMode(mode);
7 lib.install();
8
9 var main_tests = b.addTest("src/main.zig");
10 main_tests.setBuildMode(mode);
11
12 const test_step = b.step("test", "Run library tests");
13 test_step.dependOn(&main_tests.step);
14}
libs/iguanatls/gyro.zzz deleted-14
......@@ -1,14 +0,0 @@
1pkgs:
2 iguanaTLS:
3 version: 0.0.0
4 author: alexnask
5 description: "Minimal, experimental TLS 1.2 implementation in Zig"
6 license: MIT
7 source_url: "https://github.com/alexnask/iguanaTLS"
8
9 files:
10 build.zig
11 README.md
12 LICENSE
13 src/*.zig
14 test/*
libs/iguanatls/src/asn1.zig deleted-622
......@@ -1,622 +0,0 @@
1const std = @import("std");
2const BigInt = std.math.big.int.Const;
3const mem = std.mem;
4const Allocator = mem.Allocator;
5const ArenaAllocator = std.heap.ArenaAllocator;
6
7// zig fmt: off
8pub const Tag = enum(u8) {
9 bool = 0x01,
10 int = 0x02,
11 bit_string = 0x03,
12 octet_string = 0x04,
13 @"null" = 0x05,
14 object_identifier = 0x06,
15 utf8_string = 0x0c,
16 printable_string = 0x13,
17 ia5_string = 0x16,
18 utc_time = 0x17,
19 bmp_string = 0x1e,
20 sequence = 0x30,
21 set = 0x31,
22 // Bogus value
23 context_specific = 0xff,
24};
25// zig fmt: on
26
27pub const ObjectIdentifier = struct {
28 data: [16]u32,
29 len: u8,
30};
31
32pub const BitString = struct {
33 data: []const u8,
34 bit_len: usize,
35};
36
37pub const Value = union(Tag) {
38 bool: bool,
39 int: BigInt,
40 bit_string: BitString,
41 octet_string: []const u8,
42 @"null",
43 // @TODO Make this []u32, owned?
44 object_identifier: ObjectIdentifier,
45 utf8_string: []const u8,
46 printable_string: []const u8,
47 ia5_string: []const u8,
48 utc_time: []const u8,
49 bmp_string: []const u16,
50 sequence: []const @This(),
51 set: []const @This(),
52 context_specific: struct {
53 child: *const Value,
54 number: u8,
55 },
56
57 pub fn deinit(self: @This(), alloc: *Allocator) void {
58 switch (self) {
59 .int => |i| alloc.free(i.limbs),
60 .bit_string => |bs| alloc.free(bs.data),
61 .octet_string,
62 .utf8_string,
63 .printable_string,
64 .ia5_string,
65 .utc_time,
66 => |s| alloc.free(s),
67 .bmp_string => |s| alloc.free(s),
68 .sequence, .set => |s| {
69 for (s) |c| {
70 c.deinit(alloc);
71 }
72 alloc.free(s);
73 },
74 .context_specific => |cs| {
75 cs.child.deinit(alloc);
76 alloc.destroy(cs.child);
77 },
78 else => {},
79 }
80 }
81
82 fn formatInternal(
83 self: Value,
84 comptime fmt: []const u8,
85 options: std.fmt.FormatOptions,
86 indents: usize,
87 writer: anytype,
88 ) @TypeOf(writer).Error!void {
89 try writer.writeByteNTimes(' ', indents);
90 switch (self) {
91 .bool => |b| try writer.print("BOOLEAN {}\n", .{b}),
92 .int => |i| {
93 try writer.writeAll("INTEGER ");
94 try i.format(fmt, options, writer);
95 try writer.writeByte('\n');
96 },
97 .bit_string => |bs| {
98 try writer.print("BIT STRING ({} bits) ", .{bs.bit_len});
99 const bits_to_show = std.math.min(8 * 3, bs.bit_len);
100 const bytes = std.math.divCeil(usize, bits_to_show, 8) catch unreachable;
101
102 var bit_idx: usize = 0;
103 var byte_idx: usize = 0;
104 while (byte_idx < bytes) : (byte_idx += 1) {
105 const byte = bs.data[byte_idx];
106 var cur_bit_idx: u3 = 0;
107 while (bit_idx < bits_to_show) {
108 const mask = @as(u8, 0x80) >> cur_bit_idx;
109 try writer.print("{}", .{@boolToInt(byte & mask == mask)});
110 cur_bit_idx += 1;
111 bit_idx += 1;
112 if (cur_bit_idx == 7)
113 break;
114 }
115 }
116 if (bits_to_show != bs.bit_len)
117 try writer.writeAll("...");
118 try writer.writeByte('\n');
119 },
120 .octet_string => |s| try writer.print("OCTET STRING ({} bytes) {X}\n", .{ s.len, s }),
121 .@"null" => try writer.writeAll("NULL\n"),
122 .object_identifier => |oid| {
123 try writer.writeAll("OBJECT IDENTIFIER ");
124 var i: u8 = 0;
125 while (i < oid.len) : (i += 1) {
126 if (i != 0) try writer.writeByte('.');
127 try writer.print("{}", .{oid.data[i]});
128 }
129 try writer.writeByte('\n');
130 },
131 .utf8_string => |s| try writer.print("UTF8 STRING ({} bytes) {}\n", .{ s.len, s }),
132 .printable_string => |s| try writer.print("PRINTABLE STRING ({} bytes) {}\n", .{ s.len, s }),
133 .ia5_string => |s| try writer.print("IA5 STRING ({} bytes) {}\n", .{ s.len, s }),
134 .utc_time => |s| try writer.print("UTC TIME {}\n", .{s}),
135 .bmp_string => |s| try writer.print("BMP STRING ({} words) {}\n", .{
136 s.len,
137 @ptrCast([*]const u16, s.ptr)[0 .. s.len * 2],
138 }),
139 .sequence => |children| {
140 try writer.print("SEQUENCE ({} elems)\n", .{children.len});
141 for (children) |child| try child.formatInternal(fmt, options, indents + 2, writer);
142 },
143 .set => |children| {
144 try writer.print("SET ({} elems)\n", .{children.len});
145 for (children) |child| try child.formatInternal(fmt, options, indents + 2, writer);
146 },
147 .context_specific => |cs| {
148 try writer.print("[{}]\n", .{cs.number});
149 try cs.child.formatInternal(fmt, options, indents + 2, writer);
150 },
151 }
152 }
153
154 pub fn format(self: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
155 try self.formatInternal(fmt, options, 0, writer);
156 }
157};
158
159/// Distinguished encoding rules
160pub const der = struct {
161 pub fn DecodeError(comptime Reader: type) type {
162 return Reader.Error || error{
163 OutOfMemory,
164 EndOfStream,
165 InvalidLength,
166 InvalidTag,
167 InvalidContainerLength,
168 DoesNotMatchSchema,
169 };
170 }
171
172 fn DERReaderState(comptime Reader: type) type {
173 return struct {
174 der_reader: Reader,
175 length: usize,
176 idx: usize = 0,
177 };
178 }
179
180 fn DERReader(comptime Reader: type) type {
181 const S = struct {
182 pub fn read(state: *DERReaderState(Reader), buffer: []u8) DecodeError(Reader)!usize {
183 const out_bytes = std.math.min(buffer.len, state.length - state.idx);
184 const res = try state.der_reader.readAll(buffer[0..out_bytes]);
185 state.idx += res;
186 return res;
187 }
188 };
189
190 return std.io.Reader(*DERReaderState(Reader), DecodeError(Reader), S.read);
191 }
192
193 pub fn parse_schema(
194 schema: anytype,
195 captures: anytype,
196 der_reader: anytype,
197 ) !void {
198 const res = try parse_schema_tag_len_internal(null, null, schema, captures, der_reader);
199 if (res != null) return error.DoesNotMatchSchema;
200 }
201
202 pub fn parse_schema_tag_len(
203 existing_tag_byte: ?u8,
204 existing_length: ?usize,
205 schema: anytype,
206 captures: anytype,
207 der_reader: anytype,
208 ) !void {
209 const res = try parse_schema_tag_len_internal(
210 existing_tag_byte,
211 existing_length,
212 schema,
213 captures,
214 der_reader,
215 );
216 if (res != null) return error.DoesNotMatchSchema;
217 }
218
219 const TagLength = struct {
220 tag: u8,
221 length: usize,
222 };
223
224 pub fn parse_schema_tag_len_internal(
225 existing_tag_byte: ?u8,
226 existing_length: ?usize,
227 schema: anytype,
228 captures: anytype,
229 der_reader: anytype,
230 ) !?TagLength {
231 const Reader = @TypeOf(der_reader);
232
233 const isEnumLit = comptime std.meta.trait.is(.EnumLiteral);
234 comptime var tag_idx = 0;
235
236 const has_capture = comptime isEnumLit(@TypeOf(schema[tag_idx])) and schema[tag_idx] == .capture;
237 if (has_capture) tag_idx += 2;
238
239 const is_optional = comptime isEnumLit(@TypeOf(schema[tag_idx])) and schema[tag_idx] == .optional;
240 if (is_optional) tag_idx += 1;
241
242 const tag_literal = schema[tag_idx];
243 comptime std.debug.assert(isEnumLit(@TypeOf(tag_literal)));
244
245 const tag_byte = existing_tag_byte orelse (der_reader.readByte() catch |err| switch (err) {
246 error.EndOfStream => |e| return if (is_optional) null else error.EndOfStream,
247 else => |e| return e,
248 });
249
250 const length = existing_length orelse try parse_length(der_reader);
251 if (tag_literal == .sequence_of) {
252 if (tag_byte != @enumToInt(Tag.sequence)) {
253 if (is_optional) return TagLength{ .tag = tag_byte, .length = length };
254 return error.InvalidTag;
255 }
256
257 var curr_tag_length: ?TagLength = null;
258 const sub_schema = schema[tag_idx + 1];
259 while (true) {
260 if (curr_tag_length == null) {
261 curr_tag_length = .{
262 .tag = der_reader.readByte() catch |err| switch (err) {
263 error.EndOfStream => {
264 curr_tag_length = null;
265 break;
266 },
267 else => |e| return e,
268 },
269 .length = try parse_length(der_reader),
270 };
271 }
272
273 curr_tag_length = parse_schema_tag_len_internal(
274 curr_tag_length.?.tag,
275 curr_tag_length.?.length,
276 sub_schema,
277 captures,
278 der_reader,
279 ) catch |err| switch (err) {
280 error.DoesNotMatchSchema => break,
281 else => |e| return e,
282 };
283 }
284 return curr_tag_length;
285 } else if (tag_literal == .any) {
286 if (!has_capture) {
287 try der_reader.skipBytes(length, .{});
288 return null;
289 }
290
291 var reader_state = DERReaderState(Reader){
292 .der_reader = der_reader,
293 .idx = 0,
294 .length = length,
295 };
296 var reader = DERReader(@TypeOf(der_reader)){ .context = &reader_state };
297 const capture_context = captures[schema[1] * 2];
298 const capture_action = captures[schema[1] * 2 + 1];
299 try capture_action(capture_context, tag_byte, length, reader);
300
301 // Skip remaining bytes
302 try der_reader.skipBytes(reader_state.length - reader_state.idx, .{});
303 return null;
304 } else if (tag_literal == .context_specific) {
305 const cs_number = schema[tag_idx + 1];
306 if (tag_byte & 0xC0 == 0x80 and tag_byte - 0xa0 == cs_number) {
307 if (!has_capture) {
308 if (schema.len > tag_idx + 2) {
309 return try parse_schema_tag_len_internal(null, null, schema[tag_idx + 2], captures, der_reader);
310 }
311
312 try der_reader.skipBytes(length, .{});
313 return null;
314 }
315
316 var reader_state = DERReaderState(Reader){
317 .der_reader = der_reader,
318 .idx = 0,
319 .length = length,
320 };
321 var reader = DERReader(Reader){ .context = &reader_state };
322 const capture_context = captures[schema[1] * 2];
323 const capture_action = captures[schema[1] * 2 + 1];
324 try capture_action(capture_context, tag_byte, length, reader);
325
326 // Skip remaining bytes
327 try der_reader.skipBytes(reader_state.length - reader_state.idx, .{});
328 return null;
329 } else if (is_optional)
330 return TagLength{ .tag = tag_byte, .length = length }
331 else
332 return error.DoesNotMatchSchema;
333 }
334
335 const schema_tag: Tag = tag_literal;
336 const actual_tag = std.meta.intToEnum(Tag, tag_byte) catch return error.InvalidTag;
337 if (actual_tag != schema_tag) {
338 if (is_optional) return tag_byte;
339 return error.DoesNotMatchSchema;
340 }
341
342 const single_seq = schema_tag == .sequence and schema.len == 1;
343 if ((!has_capture and schema_tag != .sequence) or (!has_capture and single_seq)) {
344 try der_reader.skipBytes(length, .{});
345 return null;
346 }
347
348 if (has_capture) {
349 var reader_state = DERReaderState(Reader){
350 .der_reader = der_reader,
351 .idx = 0,
352 .length = length,
353 };
354 var reader = DERReader(Reader){ .context = &reader_state };
355 const capture_context = captures[schema[1] * 2];
356 const capture_action = captures[schema[1] * 2 + 1];
357 try capture_action(capture_context, tag_byte, length, reader);
358
359 // Skip remaining bytes
360 try der_reader.skipBytes(reader_state.length - reader_state.idx, .{});
361 return null;
362 }
363
364 var cur_tag_length: ?TagLength = null;
365 const sub_schemas = schema[tag_idx + 1];
366 comptime var i = 0;
367 inline while (i < sub_schemas.len) : (i += 1) {
368 const curr_tag = if (cur_tag_length) |tl| tl.tag else null;
369 const curr_length = if (cur_tag_length) |tl| tl.length else null;
370 cur_tag_length = try parse_schema_tag_len_internal(curr_tag, curr_length, sub_schemas[i], captures, der_reader);
371 }
372 return cur_tag_length;
373 }
374
375 pub const EncodedLength = struct {
376 data: [@sizeOf(usize) + 1]u8,
377 len: usize,
378
379 pub fn slice(self: @This()) []const u8 {
380 if (self.len == 1) return self.data[0..1];
381 return self.data[0 .. 1 + self.len];
382 }
383 };
384
385 pub fn encode_length(length: usize) EncodedLength {
386 var enc = EncodedLength{ .data = undefined, .len = 0 };
387 if (length < 128) {
388 enc.data[0] = @truncate(u8, length);
389 enc.len = 1;
390 } else {
391 const bytes_needed = @intCast(u8, std.math.divCeil(
392 usize,
393 std.math.log2_int_ceil(usize, length),
394 8,
395 ) catch unreachable);
396 enc.data[0] = bytes_needed | 0x80;
397 mem.copy(
398 u8,
399 enc.data[1 .. bytes_needed + 1],
400 mem.asBytes(&length)[0..bytes_needed],
401 );
402 if (std.builtin.endian != .Big) {
403 mem.reverse(u8, enc.data[1 .. bytes_needed + 1]);
404 }
405 enc.len = bytes_needed;
406 }
407 return enc;
408 }
409
410 fn parse_int_internal(alloc: *Allocator, bytes_read: *usize, der_reader: anytype) !BigInt {
411 const length = try parse_length_internal(bytes_read, der_reader);
412 const first_byte = try der_reader.readByte();
413 if (first_byte == 0x0 and length > 1) {
414 // Positive number with highest bit set to 1 in the rest.
415 const limb_count = std.math.divCeil(usize, length - 1, @sizeOf(usize)) catch unreachable;
416 const limbs = try alloc.alloc(usize, limb_count);
417 std.mem.set(usize, limbs, 0);
418 errdefer alloc.free(limbs);
419
420 var limb_ptr = @ptrCast([*]u8, limbs.ptr);
421 try der_reader.readNoEof(limb_ptr[0 .. length - 1]);
422 // We always reverse because the standard library big int expects little endian.
423 mem.reverse(u8, limb_ptr[0 .. length - 1]);
424
425 bytes_read.* += length;
426 return BigInt{ .limbs = limbs, .positive = true };
427 }
428 std.debug.assert(length != 0);
429 // Write first_byte
430 // Twos complement
431 const limb_count = std.math.divCeil(usize, length, @sizeOf(usize)) catch unreachable;
432 const limbs = try alloc.alloc(usize, limb_count);
433 std.mem.set(usize, limbs, 0);
434 errdefer alloc.free(limbs);
435
436 var limb_ptr = @ptrCast([*]u8, limbs.ptr);
437 limb_ptr[0] = first_byte & ~@as(u8, 0x80);
438 try der_reader.readNoEof(limb_ptr[1..length]);
439
440 // We always reverse because the standard library big int expects little endian.
441 mem.reverse(u8, limb_ptr[0..length]);
442 bytes_read.* += length;
443 return BigInt{ .limbs = limbs, .positive = (first_byte & 0x80) == 0x00 };
444 }
445
446 pub fn parse_int(alloc: *Allocator, der_reader: anytype) !BigInt {
447 var bytes: usize = undefined;
448 return try parse_int_internal(alloc, &bytes, der_reader);
449 }
450
451 pub fn parse_length(der_reader: anytype) !usize {
452 var bytes: usize = 0;
453 return try parse_length_internal(&bytes, der_reader);
454 }
455
456 fn parse_length_internal(bytes_read: *usize, der_reader: anytype) !usize {
457 const first_byte = try der_reader.readByte();
458 bytes_read.* += 1;
459 if (first_byte & 0x80 == 0x00) {
460 // 1 byte value
461 return first_byte;
462 }
463 const length = @truncate(u7, first_byte);
464 if (length > @sizeOf(usize))
465 @panic("DER length does not fit in usize");
466
467 var res_buf = std.mem.zeroes([@sizeOf(usize)]u8);
468 try der_reader.readNoEof(res_buf[0..length]);
469 bytes_read.* += length;
470
471 if (std.builtin.endian != .Big) {
472 mem.reverse(u8, res_buf[0..length]);
473 }
474 return mem.bytesToValue(usize, &res_buf);
475 }
476
477 fn parse_value_with_tag_byte(
478 tag_byte: u8,
479 alloc: *Allocator,
480 bytes_read: *usize,
481 der_reader: anytype,
482 ) DecodeError(@TypeOf(der_reader))!Value {
483 const tag = std.meta.intToEnum(Tag, tag_byte) catch {
484 // tag starts with '0b10...', this is the context specific class.
485 if (tag_byte & 0xC0 == 0x80) {
486 const length = try parse_length_internal(bytes_read, der_reader);
487 var cur_read_bytes: usize = 0;
488 var child = try alloc.create(Value);
489 errdefer alloc.destroy(child);
490
491 child.* = try parse_value_internal(alloc, &cur_read_bytes, der_reader);
492 if (cur_read_bytes != length)
493 return error.InvalidContainerLength;
494 bytes_read.* += length;
495 return Value{ .context_specific = .{ .child = child, .number = tag_byte - 0xa0 } };
496 }
497
498 return error.InvalidTag;
499 };
500 switch (tag) {
501 .bool => {
502 if ((try der_reader.readByte()) != 0x1)
503 return error.InvalidLength;
504 defer bytes_read.* += 2;
505 return Value{ .bool = (try der_reader.readByte()) != 0x0 };
506 },
507 .int => return Value{ .int = try parse_int_internal(alloc, bytes_read, der_reader) },
508 .bit_string => {
509 const length = try parse_length_internal(bytes_read, der_reader);
510 const unused_bits = try der_reader.readByte();
511 std.debug.assert(unused_bits < 8);
512 const bit_count = (length - 1) * 8 - unused_bits;
513 const bit_memory = try alloc.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable);
514 errdefer alloc.free(bit_memory);
515 try der_reader.readNoEof(bit_memory[0 .. length - 1]);
516
517 bytes_read.* += length;
518 return Value{ .bit_string = .{ .data = bit_memory, .bit_len = bit_count } };
519 },
520 .octet_string, .utf8_string, .printable_string, .utc_time, .ia5_string => {
521 const length = try parse_length_internal(bytes_read, der_reader);
522 const str_mem = try alloc.alloc(u8, length);
523 try der_reader.readNoEof(str_mem);
524 bytes_read.* += length;
525 return @as(Value, switch (tag) {
526 .octet_string => .{ .octet_string = str_mem },
527 .utf8_string => .{ .utf8_string = str_mem },
528 .printable_string => .{ .printable_string = str_mem },
529 .utc_time => .{ .utc_time = str_mem },
530 .ia5_string => .{ .ia5_string = str_mem },
531 else => unreachable,
532 });
533 },
534 .@"null" => {
535 std.debug.assert((try parse_length_internal(bytes_read, der_reader)) == 0x00);
536 return .@"null";
537 },
538 .object_identifier => {
539 const length = try parse_length_internal(bytes_read, der_reader);
540 const first_byte = try der_reader.readByte();
541 var ret = Value{ .object_identifier = .{ .data = undefined, .len = 0 } };
542 ret.object_identifier.data[0] = first_byte / 40;
543 ret.object_identifier.data[1] = first_byte % 40;
544
545 var out_idx: u8 = 2;
546 var i: usize = 0;
547 while (i < length - 1) {
548 var current_value: u32 = 0;
549 var current_byte = try der_reader.readByte();
550 i += 1;
551 while (current_byte & 0x80 == 0x80) : (i += 1) {
552 // Increase the base of the previous bytes
553 current_value *= 128;
554 // Add the current byte in base 128
555 current_value += @as(u32, current_byte & ~@as(u8, 0x80)) * 128;
556 current_byte = try der_reader.readByte();
557 } else {
558 current_value += current_byte;
559 }
560 ret.object_identifier.data[out_idx] = current_value;
561 out_idx += 1;
562 }
563 ret.object_identifier.len = out_idx;
564 std.debug.assert(out_idx <= 16);
565 bytes_read.* += length;
566 return ret;
567 },
568 .bmp_string => {
569 const length = try parse_length_internal(bytes_read, der_reader);
570 const str_mem = try alloc.alloc(u16, @divExact(length, 2));
571 errdefer alloc.free(str_mem);
572
573 for (str_mem) |*wide_char| {
574 wide_char.* = try der_reader.readIntBig(u16);
575 }
576 bytes_read.* += length;
577 return Value{ .bmp_string = str_mem };
578 },
579 .sequence, .set => {
580 const length = try parse_length_internal(bytes_read, der_reader);
581 var cur_read_bytes: usize = 0;
582 var arr = std.ArrayList(Value).init(alloc);
583 errdefer arr.deinit();
584
585 while (cur_read_bytes < length) {
586 (try arr.addOne()).* = try parse_value_internal(alloc, &cur_read_bytes, der_reader);
587 }
588 if (cur_read_bytes != length)
589 return error.InvalidContainerLength;
590 bytes_read.* += length;
591
592 return @as(Value, switch (tag) {
593 .sequence => .{ .sequence = arr.toOwnedSlice() },
594 .set => .{ .set = arr.toOwnedSlice() },
595 else => unreachable,
596 });
597 },
598 .context_specific => unreachable,
599 }
600 }
601
602 fn parse_value_internal(alloc: *Allocator, bytes_read: *usize, der_reader: anytype) DecodeError(@TypeOf(der_reader))!Value {
603 const tag_byte = try der_reader.readByte();
604 bytes_read.* += 1;
605 return try parse_value_with_tag_byte(tag_byte, alloc, bytes_read, der_reader);
606 }
607
608 pub fn parse_value(alloc: *Allocator, der_reader: anytype) DecodeError(@TypeOf(der_reader))!Value {
609 var read: usize = 0;
610 return try parse_value_internal(alloc, &read, der_reader);
611 }
612};
613
614test "der.parse_value" {
615 const github_der = @embedFile("../test/github.der");
616 var fbs = std.io.fixedBufferStream(github_der);
617
618 var arena = ArenaAllocator.init(std.testing.allocator);
619 defer arena.deinit();
620
621 _ = try der.parse_value(&arena.allocator, fbs.reader());
622}
libs/iguanatls/src/ciphersuites.zig deleted-617
......@@ -1,617 +0,0 @@
1const std = @import("std");
2const mem = std.mem;
3
4usingnamespace @import("crypto.zig");
5const Chacha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305;
6const Aes128Gcm = std.crypto.aead.aes_gcm.Aes128Gcm;
7
8const main = @import("main.zig");
9const alert_byte_to_error = main.alert_byte_to_error;
10const record_tag_length = main.record_tag_length;
11const record_length = main.record_length;
12
13pub const suites = struct {
14 pub const ECDHE_RSA_Chacha20_Poly1305 = struct {
15 pub const name = "ECDHE-RSA-CHACHA20-POLY1305";
16 pub const tag = 0xCCA8;
17 pub const key_exchange = .ecdhe;
18 pub const hash = .sha256;
19
20 pub const Keys = struct {
21 client_key: [32]u8,
22 server_key: [32]u8,
23 client_iv: [12]u8,
24 server_iv: [12]u8,
25 };
26
27 pub const State = union(enum) {
28 none,
29 in_record: struct {
30 left: usize,
31 context: ChaCha20Stream.BlockVec,
32 idx: usize,
33 buf: [64]u8,
34 },
35 };
36 pub const default_state: State = .none;
37
38 pub fn raw_write(
39 comptime buffer_size: usize,
40 rand: *std.rand.Random,
41 key_data: anytype,
42 writer: anytype,
43 prefix: [3]u8,
44 seq: u64,
45 buffer: []const u8,
46 ) !void {
47 std.debug.assert(buffer.len <= buffer_size);
48 try writer.writeAll(&prefix);
49 try writer.writeIntBig(u16, @intCast(u16, buffer.len + 16));
50
51 var additional_data: [13]u8 = undefined;
52 mem.writeIntBig(u64, additional_data[0..8], seq);
53 additional_data[8..11].* = prefix;
54 mem.writeIntBig(u16, additional_data[11..13], @intCast(u16, buffer.len));
55
56 var encrypted_data: [buffer_size]u8 = undefined;
57 var tag_data: [16]u8 = undefined;
58
59 var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8);
60 mem.writeIntBig(u64, nonce[4..12], seq);
61 for (nonce) |*n, i| {
62 n.* ^= key_data.client_iv(@This())[i];
63 }
64
65 Chacha20Poly1305.encrypt(
66 encrypted_data[0..buffer.len],
67 &tag_data,
68 buffer,
69 &additional_data,
70 nonce,
71 key_data.client_key(@This()).*,
72 );
73 try writer.writeAll(encrypted_data[0..buffer.len]);
74 try writer.writeAll(&tag_data);
75 }
76
77 pub fn check_verify_message(
78 key_data: anytype,
79 length: usize,
80 reader: anytype,
81 verify_message: [16]u8,
82 ) !bool {
83 if (length != 32)
84 return false;
85
86 var msg_in: [32]u8 = undefined;
87 try reader.readNoEof(&msg_in);
88
89 const additional_data: [13]u8 = ([1]u8{0} ** 8) ++ [5]u8{ 0x16, 0x03, 0x03, 0x00, 0x10 };
90 var decrypted: [16]u8 = undefined;
91 Chacha20Poly1305.decrypt(
92 &decrypted,
93 msg_in[0..16],
94 msg_in[16..].*,
95 &additional_data,
96 key_data.server_iv(@This()).*,
97 key_data.server_key(@This()).*,
98 ) catch return false;
99
100 return mem.eql(u8, &decrypted, &verify_message);
101 }
102
103 pub fn read(
104 comptime buf_size: usize,
105 state: *State,
106 key_data: anytype,
107 reader: anytype,
108 server_seq: *u64,
109 buffer: []u8,
110 ) !usize {
111 switch (state.*) {
112 .none => {
113 const tag_length = record_tag_length(reader) catch |err| switch (err) {
114 error.EndOfStream => return 0,
115 else => |e| return e,
116 };
117 if (tag_length.length < 16)
118 return error.ServerMalformedResponse;
119 const len = tag_length.length - 16;
120
121 if ((tag_length.tag != 0x17 and tag_length.tag != 0x15) or
122 (tag_length.tag == 0x15 and len != 2))
123 {
124 return error.ServerMalformedResponse;
125 }
126
127 const curr_bytes = if (tag_length.tag == 0x15)
128 2
129 else
130 std.math.min(std.math.min(len, buf_size), buffer.len);
131
132 var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8);
133 mem.writeIntBig(u64, nonce[4..12], server_seq.*);
134 for (nonce) |*n, i| {
135 n.* ^= key_data.server_iv(@This())[i];
136 }
137
138 var c: [4]u32 = undefined;
139 c[0] = 1;
140 c[1] = mem.readIntLittle(u32, nonce[0..4]);
141 c[2] = mem.readIntLittle(u32, nonce[4..8]);
142 c[3] = mem.readIntLittle(u32, nonce[8..12]);
143 const server_key = keyToWords(key_data.server_key(@This()).*);
144 var context = ChaCha20Stream.initContext(server_key, c);
145 var idx: usize = 0;
146 var buf: [64]u8 = undefined;
147
148 if (tag_length.tag == 0x15) {
149 var encrypted: [2]u8 = undefined;
150 reader.readNoEof(&encrypted) catch |err| switch (err) {
151 error.EndOfStream => return error.ServerMalformedResponse,
152 else => |e| return e,
153 };
154 var result: [2] u8 = undefined;
155 ChaCha20Stream.chacha20Xor(
156 &result,
157 &encrypted,
158 server_key,
159 &context,
160 &idx,
161 &buf,
162 );
163 reader.skipBytes(16, .{}) catch |err| switch (err) {
164 error.EndOfStream => return error.ServerMalformedResponse,
165 else => |e| return e,
166 };
167 server_seq.* += 1;
168 // CloseNotify
169 if (result[1] == 0)
170 return 0;
171 return alert_byte_to_error(result[1]);
172 } else if (tag_length.tag == 0x17) {
173 // Partially decrypt the data.
174 var encrypted: [buf_size]u8 = undefined;
175 const actually_read = try reader.read(encrypted[0..curr_bytes]);
176
177 ChaCha20Stream.chacha20Xor(
178 buffer[0..actually_read],
179 encrypted[0..actually_read],
180 server_key,
181 &context,
182 &idx,
183 &buf,
184 );
185 if (actually_read < len) {
186 state.* = .{
187 .in_record = .{
188 .left = len - actually_read,
189 .context = context,
190 .idx = idx,
191 .buf = buf,
192 },
193 };
194 } else {
195 // @TODO Verify Poly1305.
196 reader.skipBytes(16, .{}) catch |err| switch (err) {
197 error.EndOfStream => return error.ServerMalformedResponse,
198 else => |e| return e,
199 };
200 server_seq.* += 1;
201 }
202 return actually_read;
203 } else unreachable;
204 },
205 .in_record => |*record_info| {
206 const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);
207 // Partially decrypt the data.
208 var encrypted: [buf_size]u8 = undefined;
209 const actually_read = try reader.read(encrypted[0..curr_bytes]);
210 ChaCha20Stream.chacha20Xor(
211 buffer[0..actually_read],
212 encrypted[0..actually_read],
213 keyToWords(key_data.server_key(@This()).*),
214 &record_info.context,
215 &record_info.idx,
216 &record_info.buf,
217 );
218
219 record_info.left -= actually_read;
220 if (record_info.left == 0) {
221 // @TODO Verify Poly1305.
222 reader.skipBytes(16, .{}) catch |err| switch (err) {
223 error.EndOfStream => return error.ServerMalformedResponse,
224 else => |e| return e,
225 };
226 state.* = .none;
227 server_seq.* += 1;
228 }
229 return actually_read;
230 },
231 }
232 }
233 };
234
235 pub const ECDHE_RSA_AES128_GCM_SHA256 = struct {
236 pub const name = "ECDHE-RSA-AES128-GCM-SHA256";
237 pub const tag = 0xC02F;
238 pub const key_exchange = .ecdhe;
239 pub const hash = .sha256;
240
241 pub const Keys = struct {
242 client_key: [16]u8,
243 server_key: [16]u8,
244 client_iv: [4]u8,
245 server_iv: [4]u8,
246 };
247
248 const Aes = std.crypto.core.aes.Aes128;
249 pub const State = union(enum) {
250 none,
251 in_record: struct {
252 left: usize,
253 aes: @typeInfo(@TypeOf(Aes.initEnc)).Fn.return_type.?,
254 // ctr state
255 counterInt: u128,
256 idx: usize,
257 },
258 };
259 pub const default_state: State = .none;
260
261 pub fn check_verify_message(
262 key_data: anytype,
263 length: usize,
264 reader: anytype,
265 verify_message: [16]u8,
266 ) !bool {
267 if (length != 40)
268 return false;
269
270 var iv: [12]u8 = undefined;
271 iv[0..4].* = key_data.server_iv(@This()).*;
272 try reader.readNoEof(iv[4..12]);
273
274 var msg_in: [32]u8 = undefined;
275 try reader.readNoEof(&msg_in);
276
277 const additional_data: [13]u8 = ([1]u8{0} ** 8) ++ [5]u8{ 0x16, 0x03, 0x03, 0x00, 0x10 };
278 var decrypted: [16]u8 = undefined;
279 Aes128Gcm.decrypt(
280 &decrypted,
281 msg_in[0..16],
282 msg_in[16..].*,
283 &additional_data,
284 iv,
285 key_data.server_key(@This()).*,
286 ) catch return false;
287
288 return mem.eql(u8, &decrypted, &verify_message);
289 }
290
291 pub fn raw_write(
292 comptime buffer_size: usize,
293 rand: *std.rand.Random,
294 key_data: anytype,
295 writer: anytype,
296 prefix: [3]u8,
297 seq: u64,
298 buffer: []const u8,
299 ) !void {
300 std.debug.assert(buffer.len <= buffer_size);
301 var iv: [12]u8 = undefined;
302 iv[0..4].* = key_data.client_iv(@This()).*;
303 rand.bytes(iv[4..12]);
304
305 var additional_data: [13]u8 = undefined;
306 mem.writeIntBig(u64, additional_data[0..8], seq);
307 additional_data[8..11].* = prefix;
308 mem.writeIntBig(u16, additional_data[11..13], @intCast(u16, buffer.len));
309
310 try writer.writeAll(&prefix);
311 try writer.writeIntBig(u16, @intCast(u16, buffer.len + 24));
312 try writer.writeAll(iv[4..12]);
313
314 var encrypted_data: [buffer_size]u8 = undefined;
315 var tag_data: [16]u8 = undefined;
316
317 Aes128Gcm.encrypt(
318 encrypted_data[0..buffer.len],
319 &tag_data,
320 buffer,
321 &additional_data,
322 iv,
323 key_data.client_key(@This()).*,
324 );
325 try writer.writeAll(encrypted_data[0..buffer.len]);
326 try writer.writeAll(&tag_data);
327 }
328
329 pub fn read(
330 comptime buf_size: usize,
331 state: *State,
332 key_data: anytype,
333 reader: anytype,
334 server_seq: *u64,
335 buffer: []u8,
336 ) !usize {
337 switch (state.*) {
338 .none => {
339 const tag_length = record_tag_length(reader) catch |err| switch (err) {
340 error.EndOfStream => return 0,
341 else => |e| return e,
342 };
343 if (tag_length.length < 24)
344 return error.ServerMalformedResponse;
345 const len = tag_length.length - 24;
346
347 if ((tag_length.tag != 0x17 and tag_length.tag != 0x15) or
348 (tag_length.tag == 0x15 and len != 2))
349 {
350 return error.ServerMalformedResponse;
351 }
352
353 const curr_bytes = if (tag_length.tag == 0x15)
354 2
355 else
356 std.math.min(std.math.min(len, buf_size), buffer.len);
357
358 var iv: [12]u8 = undefined;
359 iv[0..4].* = key_data.server_iv(@This()).*;
360 reader.readNoEof(iv[4..12]) catch |err| switch (err) {
361 error.EndOfStream => return 0,
362 else => |e| return e,
363 };
364
365 const aes = Aes.initEnc(key_data.server_key(@This()).*);
366
367 var j: [16]u8 = undefined;
368 mem.copy(u8, j[0..12], iv[0..]);
369 mem.writeIntBig(u32, j[12..][0..4], 2);
370
371 var counterInt = mem.readInt(u128, &j, .Big);
372 var idx: usize = 0;
373
374 if (tag_length.tag == 0x15) {
375 var encrypted: [2]u8 = undefined;
376 reader.readNoEof(&encrypted) catch |err| switch (err) {
377 error.EndOfStream => return error.ServerMalformedResponse,
378 else => |e| return e,
379 };
380
381 var result: [2]u8 = undefined;
382 ctr(
383 @TypeOf(aes),
384 aes,
385 &result,
386 &encrypted,
387 &counterInt,
388 &idx,
389 .Big,
390 );
391 reader.skipBytes(16, .{}) catch |err| switch (err) {
392 error.EndOfStream => return error.ServerMalformedResponse,
393 else => |e| return e,
394 };
395 server_seq.* += 1;
396 // CloseNotify
397 if (result[1] == 0)
398 return 0;
399 return alert_byte_to_error(result[1]);
400 } else if (tag_length.tag == 0x17) {
401 // Partially decrypt the data.
402 var encrypted: [buf_size]u8 = undefined;
403 const actually_read = try reader.read(encrypted[0..curr_bytes]);
404
405 ctr(
406 @TypeOf(aes),
407 aes,
408 buffer[0..actually_read],
409 encrypted[0..actually_read],
410 &counterInt,
411 &idx,
412 .Big,
413 );
414
415 if (actually_read < len) {
416 state.* = .{
417 .in_record = .{
418 .left = len - actually_read,
419 .aes = aes,
420 .counterInt = counterInt,
421 .idx = idx,
422 },
423 };
424 } else {
425 // @TODO Verify the message
426 reader.skipBytes(16, .{}) catch |err| switch (err) {
427 error.EndOfStream => return error.ServerMalformedResponse,
428 else => |e| return e,
429 };
430 server_seq.* += 1;
431 }
432 return actually_read;
433 } else unreachable;
434 },
435 .in_record => |*record_info| {
436 const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);
437 // Partially decrypt the data.
438 var encrypted: [buf_size]u8 = undefined;
439 const actually_read = try reader.read(encrypted[0..curr_bytes]);
440
441 ctr(
442 @TypeOf(record_info.aes),
443 record_info.aes,
444 buffer[0..actually_read],
445 encrypted[0..actually_read],
446 &record_info.counterInt,
447 &record_info.idx,
448 .Big,
449 );
450 record_info.left -= actually_read;
451 if (record_info.left == 0) {
452 // @TODO Verify Poly1305.
453 reader.skipBytes(16, .{}) catch |err| switch (err) {
454 error.EndOfStream => return error.ServerMalformedResponse,
455 else => |e| return e,
456 };
457 state.* = .none;
458 server_seq.* += 1;
459 }
460 return actually_read;
461 },
462 }
463 }
464 };
465
466 pub const all = &[_]type{ ECDHE_RSA_Chacha20_Poly1305, ECDHE_RSA_AES128_GCM_SHA256 };
467};
468
469fn key_field_width(comptime T: type, comptime field: anytype) ?usize {
470 if (!@hasField(T, @tagName(field)))
471 return null;
472
473 const field_info = std.meta.fieldInfo(T, field);
474 if (!comptime std.meta.trait.is(.Array)(field_info.field_type) or std.meta.Elem(field_info.field_type) != u8)
475 @compileError("Field '" ++ field ++ "' of type '" ++ @typeName(T) ++ "' should be an array of u8.");
476
477 return @typeInfo(field_info.field_type).Array.len;
478}
479
480pub fn key_data_size(comptime ciphersuites: anytype) usize {
481 var max: usize = 0;
482 for (ciphersuites) |cs| {
483 const curr = (key_field_width(cs.Keys, .client_mac) orelse 0) +
484 (key_field_width(cs.Keys, .server_mac) orelse 0) +
485 key_field_width(cs.Keys, .client_key).? +
486 key_field_width(cs.Keys, .server_key).? +
487 key_field_width(cs.Keys, .client_iv).? +
488 key_field_width(cs.Keys, .server_iv).?;
489 if (curr > max)
490 max = curr;
491 }
492 return max;
493}
494
495pub fn KeyData(comptime ciphersuites: anytype) type {
496 return struct {
497 data: [key_data_size(ciphersuites)]u8,
498
499 pub fn client_mac(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_mac) orelse 0]u8 {
500 return self.data[0..comptime (key_field_width(cs.Keys, .client_mac) orelse 0)];
501 }
502
503 pub fn server_mac(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_mac) orelse 0]u8 {
504 const start = key_field_width(cs.Keys, .client_mac) orelse 0;
505 return self.data[start..][0..comptime (key_field_width(cs.Keys, .server_mac) orelse 0)];
506 }
507
508 pub fn client_key(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_key).?]u8 {
509 const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
510 (key_field_width(cs.Keys, .server_mac) orelse 0);
511 return self.data[start..][0..comptime key_field_width(cs.Keys, .client_key).?];
512 }
513
514 pub fn server_key(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_key).?]u8 {
515 const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
516 (key_field_width(cs.Keys, .server_mac) orelse 0) +
517 key_field_width(cs.Keys, .client_key).?;
518 return self.data[start..][0..comptime key_field_width(cs.Keys, .server_key).?];
519 }
520
521 pub fn client_iv(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_iv).?]u8 {
522 const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
523 (key_field_width(cs.Keys, .server_mac) orelse 0) +
524 key_field_width(cs.Keys, .client_key).? +
525 key_field_width(cs.Keys, .server_key).?;
526 return self.data[start..][0..comptime key_field_width(cs.Keys, .client_iv).?];
527 }
528
529 pub fn server_iv(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_iv).?]u8 {
530 const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
531 (key_field_width(cs.Keys, .server_mac) orelse 0) +
532 key_field_width(cs.Keys, .client_key).? +
533 key_field_width(cs.Keys, .server_key).? +
534 key_field_width(cs.Keys, .client_iv).?;
535 return self.data[start..][0..comptime key_field_width(cs.Keys, .server_iv).?];
536 }
537 };
538}
539
540pub fn key_expansion(
541 comptime ciphersuites: anytype,
542 tag: u16,
543 context: anytype,
544 comptime next_32_bytes: anytype,
545) KeyData(ciphersuites) {
546 var res: KeyData(ciphersuites) = undefined;
547 inline for (ciphersuites) |cs| {
548 if (cs.tag == tag) {
549 var chunk: [32]u8 = undefined;
550 next_32_bytes(context, 0, &chunk);
551 comptime var chunk_idx = 1;
552 comptime var data_cursor = 0;
553 comptime var chunk_cursor = 0;
554
555 const fields = .{
556 .client_mac, .server_mac,
557 .client_key, .server_key,
558 .client_iv, .server_iv,
559 };
560 inline for (fields) |field| {
561 if (chunk_cursor == 32) {
562 next_32_bytes(context, chunk_idx, &chunk);
563 chunk_idx += 1;
564 chunk_cursor = 0;
565 }
566
567 const field_width = comptime (key_field_width(cs.Keys, field) orelse 0);
568 const first_read = comptime std.math.min(32 - chunk_cursor, field_width);
569 const second_read = field_width - first_read;
570
571 res.data[data_cursor..][0..first_read].* = chunk[chunk_cursor..][0..first_read].*;
572 data_cursor += first_read;
573 chunk_cursor += first_read;
574
575 if (second_read != 0) {
576 next_32_bytes(context, chunk_idx, &chunk);
577 chunk_idx += 1;
578 res.data[data_cursor..][0..second_read].* = chunk[chunk_cursor..][0..second_read].*;
579 data_cursor += second_read;
580 chunk_cursor = second_read;
581 comptime std.debug.assert(chunk_cursor != 32);
582 }
583 }
584
585 return res;
586 }
587 }
588 unreachable;
589}
590
591pub fn ClientState(comptime ciphersuites: anytype) type {
592 var fields: [ciphersuites.len]std.builtin.TypeInfo.UnionField = undefined;
593 for (ciphersuites) |cs, i| {
594 fields[i] = .{
595 .name = cs.name,
596 .field_type = cs.State,
597 .alignment = if (@sizeOf(cs.State) > 0) @alignOf(cs.State) else 0,
598 };
599 }
600 return @Type(.{
601 .Union = .{
602 .layout = .Extern,
603 .tag_type = null,
604 .fields = &fields,
605 .decls = &[0]std.builtin.TypeInfo.Declaration{},
606 },
607 });
608}
609
610pub fn client_state_default(comptime ciphersuites: anytype, tag: u16) ClientState(ciphersuites) {
611 inline for (ciphersuites) |cs| {
612 if (cs.tag == tag) {
613 return @unionInit(ClientState(ciphersuites), cs.name, cs.default_state);
614 }
615 }
616 unreachable;
617}
libs/iguanatls/src/crypto.zig deleted-984
......@@ -1,984 +0,0 @@
1const std = @import("std");
2const mem = std.mem;
3
4// TODO See stdlib, this is a modified non vectorized implementation
5pub const ChaCha20Stream = struct {
6 const math = std.math;
7 pub const BlockVec = [16]u32;
8
9 pub fn initContext(key: [8]u32, d: [4]u32) BlockVec {
10 const c = "expand 32-byte k";
11 const constant_le = comptime [4]u32{
12 mem.readIntLittle(u32, c[0..4]),
13 mem.readIntLittle(u32, c[4..8]),
14 mem.readIntLittle(u32, c[8..12]),
15 mem.readIntLittle(u32, c[12..16]),
16 };
17 return BlockVec{
18 constant_le[0], constant_le[1], constant_le[2], constant_le[3],
19 key[0], key[1], key[2], key[3],
20 key[4], key[5], key[6], key[7],
21 d[0], d[1], d[2], d[3],
22 };
23 }
24
25 const QuarterRound = struct {
26 a: usize,
27 b: usize,
28 c: usize,
29 d: usize,
30 };
31
32 fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
33 return QuarterRound{
34 .a = a,
35 .b = b,
36 .c = c,
37 .d = d,
38 };
39 }
40
41 inline fn chacha20Core(x: *BlockVec, input: BlockVec) void {
42 x.* = input;
43
44 const rounds = comptime [_]QuarterRound{
45 Rp(0, 4, 8, 12),
46 Rp(1, 5, 9, 13),
47 Rp(2, 6, 10, 14),
48 Rp(3, 7, 11, 15),
49 Rp(0, 5, 10, 15),
50 Rp(1, 6, 11, 12),
51 Rp(2, 7, 8, 13),
52 Rp(3, 4, 9, 14),
53 };
54
55 comptime var j: usize = 0;
56 inline while (j < 20) : (j += 2) {
57 inline for (rounds) |r| {
58 x[r.a] +%= x[r.b];
59 x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16));
60 x[r.c] +%= x[r.d];
61 x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12));
62 x[r.a] +%= x[r.b];
63 x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8));
64 x[r.c] +%= x[r.d];
65 x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));
66 }
67 }
68 }
69
70 inline fn hashToBytes(out: *[64]u8, x: BlockVec) void {
71 var i: usize = 0;
72 while (i < 4) : (i += 1) {
73 mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]);
74 mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]);
75 mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]);
76 mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]);
77 }
78 }
79
80 inline fn contextFeedback(x: *BlockVec, ctx: BlockVec) void {
81 var i: usize = 0;
82 while (i < 16) : (i += 1) {
83 x[i] +%= ctx[i];
84 }
85 }
86
87 // TODO: Optimize this
88 pub fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, ctx: *BlockVec, idx: *usize, buf: *[64]u8) void {
89 var x: BlockVec = undefined;
90
91 const start_idx = idx.*;
92 var i: usize = 0;
93 while (i < in.len) {
94 if (idx.* % 64 == 0) {
95 if (idx.* != 0) {
96 ctx.*[12] += 1;
97 }
98 chacha20Core(x[0..], ctx.*);
99 contextFeedback(&x, ctx.*);
100 hashToBytes(buf, x);
101 }
102
103 out[i] = in[i] ^ buf[idx.* % 64];
104
105 i += 1;
106 idx.* += 1;
107 }
108 }
109};
110
111pub fn keyToWords(key: [32]u8) [8]u32 {
112 var k: [8]u32 = undefined;
113 var i: usize = 0;
114 while (i < 8) : (i += 1) {
115 k[i] = mem.readIntLittle(u32, key[i * 4 ..][0..4]);
116 }
117 return k;
118}
119
120// See std.crypto.core.modes.ctr
121/// This mode creates a key stream by encrypting an incrementing counter using a block cipher, and adding it to the source material.
122pub fn ctr(
123 comptime BlockCipher: anytype,
124 block_cipher: BlockCipher,
125 dst: []u8,
126 src: []const u8,
127 counterInt: *u128,
128 idx: *usize,
129 endian: comptime std.builtin.Endian,
130) void {
131 std.debug.assert(dst.len >= src.len);
132 const block_length = BlockCipher.block_length;
133 var cur_idx: usize = 0;
134
135 const offset = idx.* % block_length;
136 if (offset != 0) {
137 const part_len = std.math.min(block_length - offset, src.len);
138
139 var counter: [BlockCipher.block_length]u8 = undefined;
140 mem.writeInt(u128, &counter, counterInt.*, endian);
141 var pad = [_]u8{0} ** block_length;
142 mem.copy(u8, pad[offset..], src[0..part_len]);
143 block_cipher.xor(&pad, &pad, counter);
144 mem.copy(u8, dst[0..part_len], pad[offset..][0..part_len]);
145
146 cur_idx += part_len;
147 idx.* += part_len;
148 if (idx.* % block_length == 0)
149 counterInt.* += 1;
150 }
151
152 const start_idx = cur_idx;
153 const remaining = src.len - cur_idx;
154 cur_idx = 0;
155
156 const parallel_count = BlockCipher.block.parallel.optimal_parallel_blocks;
157 const wide_block_length = parallel_count * 16;
158 if (remaining >= wide_block_length) {
159 var counters: [parallel_count * 16]u8 = undefined;
160 while (cur_idx + wide_block_length <= remaining) : (cur_idx += wide_block_length) {
161 comptime var j = 0;
162 inline while (j < parallel_count) : (j += 1) {
163 mem.writeInt(u128, counters[j * 16 .. j * 16 + 16], counterInt.*, endian);
164 counterInt.* +%= 1;
165 }
166 block_cipher.xorWide(parallel_count, dst[start_idx..][cur_idx .. cur_idx + wide_block_length][0..wide_block_length], src[start_idx..][cur_idx .. cur_idx + wide_block_length][0..wide_block_length], counters);
167 idx.* += wide_block_length;
168 }
169 }
170 while (cur_idx + block_length <= remaining) : (cur_idx += block_length) {
171 var counter: [BlockCipher.block_length]u8 = undefined;
172 mem.writeInt(u128, &counter, counterInt.*, endian);
173 counterInt.* +%= 1;
174 block_cipher.xor(dst[start_idx..][cur_idx .. cur_idx + block_length][0..block_length], src[start_idx..][cur_idx .. cur_idx + block_length][0..block_length], counter);
175 idx.* += block_length;
176 }
177 if (cur_idx < remaining) {
178 std.debug.assert(idx.* % block_length == 0);
179 var counter: [BlockCipher.block_length]u8 = undefined;
180 mem.writeInt(u128, &counter, counterInt.*, endian);
181
182 var pad = [_]u8{0} ** block_length;
183 mem.copy(u8, &pad, src[start_idx..][cur_idx..]);
184 block_cipher.xor(&pad, &pad, counter);
185 mem.copy(u8, dst[cur_idx..], pad[0 .. remaining - cur_idx]);
186
187 idx.* += remaining - cur_idx;
188 if (idx.* % block_length == 0)
189 counterInt.* +%= 1;
190 }
191}
192
193// Ported from BearSSL's ec_prime_i31 engine
194pub const ecc = struct {
195 pub const SECP384R1 = struct {
196 pub const point_len = 96;
197
198 const order = [point_len / 2]u8{
199 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
200 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
201 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
202 0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF,
203 0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A,
204 0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73,
205 };
206
207 const P = [_]u32{
208 0x0000018C, 0x7FFFFFFF, 0x00000001, 0x00000000,
209 0x7FFFFFF8, 0x7FFFFFEF, 0x7FFFFFFF, 0x7FFFFFFF,
210 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF,
211 0x7FFFFFFF, 0x00000FFF,
212 };
213 const R2 = [_]u32{
214 0x0000018C, 0x00000000, 0x00000080, 0x7FFFFE00,
215 0x000001FF, 0x00000800, 0x00000000, 0x7FFFE000,
216 0x00001FFF, 0x00008000, 0x00008000, 0x00000000,
217 0x00000000, 0x00000000,
218 };
219 const B = [_]u32{
220 0x0000018C, 0x6E666840, 0x070D0392, 0x5D810231,
221 0x7651D50C, 0x17E218D6, 0x1B192002, 0x44EFE441,
222 0x3A524E2B, 0x2719BA5F, 0x41F02209, 0x36C5643E,
223 0x5813EFFE, 0x000008A5,
224 };
225
226 const base_point = [point_len]u8{
227 0xAA, 0x87, 0xCA, 0x22, 0xBE, 0x8B, 0x05, 0x37,
228 0x8E, 0xB1, 0xC7, 0x1E, 0xF3, 0x20, 0xAD, 0x74,
229 0x6E, 0x1D, 0x3B, 0x62, 0x8B, 0xA7, 0x9B, 0x98,
230 0x59, 0xF7, 0x41, 0xE0, 0x82, 0x54, 0x2A, 0x38,
231 0x55, 0x02, 0xF2, 0x5D, 0xBF, 0x55, 0x29, 0x6C,
232 0x3A, 0x54, 0x5E, 0x38, 0x72, 0x76, 0x0A, 0xB7,
233 0x36, 0x17, 0xDE, 0x4A, 0x96, 0x26, 0x2C, 0x6F,
234 0x5D, 0x9E, 0x98, 0xBF, 0x92, 0x92, 0xDC, 0x29,
235 0xF8, 0xF4, 0x1D, 0xBD, 0x28, 0x9A, 0x14, 0x7C,
236 0xE9, 0xDA, 0x31, 0x13, 0xB5, 0xF0, 0xB8, 0xC0,
237 0x0A, 0x60, 0xB1, 0xCE, 0x1D, 0x7E, 0x81, 0x9D,
238 0x7A, 0x43, 0x1D, 0x7C, 0x90, 0xEA, 0x0E, 0x5F,
239 };
240
241 comptime {
242 std.debug.assert((P[0] - (P[0] >> 5) + 7) >> 2 == point_len + 1);
243 }
244 };
245
246 pub const SECP256R1 = struct {
247 pub const point_len = 64;
248
249 const order = [point_len / 2]u8{
250 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
251 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
252 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
253 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51,
254 };
255
256 const P = [_]u32{
257 0x00000108, 0x7FFFFFFF,
258 0x7FFFFFFF, 0x7FFFFFFF,
259 0x00000007, 0x00000000,
260 0x00000000, 0x00000040,
261 0x7FFFFF80, 0x000000FF,
262 };
263 const R2 = [_]u32{
264 0x00000108, 0x00014000,
265 0x00018000, 0x00000000,
266 0x7FF40000, 0x7FEFFFFF,
267 0x7FF7FFFF, 0x7FAFFFFF,
268 0x005FFFFF, 0x00000000,
269 };
270 const B = [_]u32{
271 0x00000108, 0x6FEE1803,
272 0x6229C4BD, 0x21B139BE,
273 0x327150AA, 0x3567802E,
274 0x3F7212ED, 0x012E4355,
275 0x782DD38D, 0x0000000E,
276 };
277
278 const base_point = [point_len]u8{
279 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47,
280 0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40, 0xF2,
281 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0,
282 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96,
283 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B,
284 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16,
285 0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE,
286 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5,
287 };
288
289 comptime {
290 std.debug.assert((P[0] - (P[0] >> 5) + 7) >> 2 == point_len + 1);
291 }
292 };
293
294 fn jacobian_len(comptime Curve: type) usize {
295 return @divTrunc(Curve.order.len * 8 + 61, 31);
296 }
297
298 fn Jacobian(comptime Curve: type) type {
299 return [3][jacobian_len(Curve)]u32;
300 }
301
302 fn zero_jacobian(comptime Curve: type) Jacobian(Curve) {
303 var result = std.mem.zeroes(Jacobian(Curve));
304 result[0][0] = Curve.P[0];
305 result[1][0] = Curve.P[0];
306 result[2][0] = Curve.P[0];
307 return result;
308 }
309
310 pub fn scalarmult(
311 comptime Curve: type,
312 point: [Curve.point_len]u8,
313 k: []const u8,
314 ) ![Curve.point_len]u8 {
315 var P: Jacobian(Curve) = undefined;
316 var res: u32 = decode_to_jacobian(Curve, &P, point);
317 point_mul(Curve, &P, k);
318 var out: [Curve.point_len]u8 = undefined;
319 encode_from_jacobian(Curve, &out, P);
320 if (res == 0)
321 return error.MultiplicationFailed;
322 return out;
323 }
324
325 pub fn KeyPair(comptime Curve: type) type {
326 return struct {
327 public_key: [Curve.point_len]u8,
328 secret_key: [Curve.point_len / 2]u8,
329 };
330 }
331
332 pub fn make_key_pair(comptime Curve: type, rand_bytes: [Curve.point_len / 2]u8) KeyPair(Curve) {
333 var key_bytes = rand_bytes;
334 comptime var mask: u8 = 0xFF;
335 comptime {
336 while (mask >= Curve.order[0]) {
337 mask >>= 1;
338 }
339 }
340 key_bytes[0] &= mask;
341 key_bytes[Curve.point_len / 2 - 1] |= 0x01;
342
343 return .{
344 .secret_key = key_bytes,
345 .public_key = scalarmult(Curve, Curve.base_point, &key_bytes) catch unreachable,
346 };
347 }
348
349 fn jacobian_with_one_set(comptime Curve: type, comptime fields: [2][jacobian_len(Curve)]u32) Jacobian(Curve) {
350 comptime const plen = (Curve.P[0] + 63) >> 5;
351 return fields ++ [1][jacobian_len(Curve)]u32{
352 [2]u32{ Curve.P[0], 1 } ++ ([1]u32{0} ** (plen - 2)),
353 };
354 }
355
356 fn encode_from_jacobian(comptime Curve: type, point: *[Curve.point_len]u8, P: Jacobian(Curve)) void {
357 var Q = P;
358 const T = comptime jacobian_with_one_set(Curve, [2][jacobian_len(Curve)]u32{ undefined, undefined });
359 _ = run_code(Curve, &Q, T, &code.affine);
360 encode_jacobian_part(Curve, point[0 .. Curve.point_len / 2], Q[0]);
361 encode_jacobian_part(Curve, point[Curve.point_len / 2 ..], Q[1]);
362 }
363
364 fn point_mul(comptime Curve: type, P: *Jacobian(Curve), x: []const u8) void {
365 var P2 = P.*;
366 point_double(Curve, &P2);
367 var P3 = P.*;
368 point_add(Curve, &P3, P2);
369 var Q = zero_jacobian(Curve);
370 var qz: u32 = 1;
371 var xlen = x.len;
372 var xidx: usize = 0;
373 while (xlen > 0) : ({
374 xlen -= 1;
375 xidx += 1;
376 }) {
377 var k: u3 = 6;
378 while (true) : (k -= 2) {
379 point_double(Curve, &Q);
380 point_double(Curve, &Q);
381 var T = P.*;
382 var U = Q;
383 const bits = @as(u32, x[xidx] >> k) & 3;
384 const bnz = NEQ(bits, 0);
385 CCOPY(EQ(bits, 2), mem.asBytes(&T), mem.asBytes(&P2));
386 CCOPY(EQ(bits, 3), mem.asBytes(&T), mem.asBytes(&P3));
387 point_add(Curve, &U, T);
388 CCOPY(bnz & qz, mem.asBytes(&Q), mem.asBytes(&T));
389 CCOPY(bnz & ~qz, mem.asBytes(&Q), mem.asBytes(&U));
390 qz &= ~bnz;
391
392 if (k == 0)
393 break;
394 }
395 }
396 P.* = Q;
397 }
398
399 inline fn point_double(comptime Curve: type, P: *Jacobian(Curve)) void {
400 _ = run_code(Curve, P, P.*, &code.double);
401 }
402 inline fn point_add(comptime Curve: type, P1: *Jacobian(Curve), P2: Jacobian(Curve)) void {
403 _ = run_code(Curve, P1, P2, &code._add);
404 }
405
406 fn decode_to_jacobian(
407 comptime Curve: type,
408 out: *Jacobian(Curve),
409 point: [Curve.point_len]u8,
410 ) u32 {
411 out.* = zero_jacobian(Curve);
412 var result = decode_mod(Curve, &out.*[0], point[0 .. Curve.point_len / 2].*);
413 result &= decode_mod(Curve, &out.*[1], point[Curve.point_len / 2 ..].*);
414
415 const zlen = comptime ((Curve.P[0] + 63) >> 5);
416 comptime std.debug.assert(zlen == @typeInfo(@TypeOf(Curve.R2)).Array.len);
417 comptime std.debug.assert(zlen == @typeInfo(@TypeOf(Curve.B)).Array.len);
418
419 const Q = comptime jacobian_with_one_set(Curve, [2][jacobian_len(Curve)]u32{ Curve.R2, Curve.B });
420 result &= ~run_code(Curve, out, Q, &code.check);
421 return result;
422 }
423
424 const code = struct {
425 const P1x = 0;
426 const P1y = 1;
427 const P1z = 2;
428 const P2x = 3;
429 const P2y = 4;
430 const P2z = 5;
431 const Px = 0;
432 const Py = 1;
433 const Pz = 2;
434 const t1 = 6;
435 const t2 = 7;
436 const t3 = 8;
437 const t4 = 9;
438 const t5 = 10;
439 const t6 = 11;
440 const t7 = 12;
441 const t8 = 3;
442 const t9 = 4;
443 const t10 = 5;
444 fn MSET(comptime d: u16, comptime a: u16) u16 {
445 return 0x0000 + (d << 8) + (a << 4);
446 }
447 fn MADD(comptime d: u16, comptime a: u16) u16 {
448 return 0x1000 + (d << 8) + (a << 4);
449 }
450 fn MSUB(comptime d: u16, comptime a: u16) u16 {
451 return 0x2000 + (d << 8) + (a << 4);
452 }
453 fn MMUL(comptime d: u16, comptime a: u16, comptime b: u16) u16 {
454 return 0x3000 + (d << 8) + (a << 4) + b;
455 }
456 fn MINV(comptime d: u16, comptime a: u16, comptime b: u16) u16 {
457 return 0x4000 + (d << 8) + (a << 4) + b;
458 }
459 fn MTZ(comptime d: u16) u16 {
460 return 0x5000 + (d << 8);
461 }
462 const ENDCODE = 0;
463
464 const check = [_]u16{
465 // Convert x and y to Montgomery representation.
466 MMUL(t1, P1x, P2x),
467 MMUL(t2, P1y, P2x),
468 MSET(P1x, t1),
469 MSET(P1y, t2),
470 // Compute x^3 in t1.
471 MMUL(t2, P1x, P1x),
472 MMUL(t1, P1x, t2),
473 // Subtract 3*x from t1.
474 MSUB(t1, P1x),
475 MSUB(t1, P1x),
476 MSUB(t1, P1x),
477 // Add b.
478 MADD(t1, P2y),
479 // Compute y^2 in t2.
480 MMUL(t2, P1y, P1y),
481 // Compare y^2 with x^3 - 3*x + b; they must match.
482 MSUB(t1, t2),
483 MTZ(t1),
484 // Set z to 1 (in Montgomery representation).
485 MMUL(P1z, P2x, P2z),
486 ENDCODE,
487 };
488 const double = [_]u16{
489 // Compute z^2 (in t1).
490 MMUL(t1, Pz, Pz),
491 // Compute x-z^2 (in t2) and then x+z^2 (in t1).
492 MSET(t2, Px),
493 MSUB(t2, t1),
494 MADD(t1, Px),
495 // Compute m = 3*(x+z^2)*(x-z^2) (in t1).
496 MMUL(t3, t1, t2),
497 MSET(t1, t3),
498 MADD(t1, t3),
499 MADD(t1, t3),
500 // Compute s = 4*x*y^2 (in t2) and 2*y^2 (in t3).
501 MMUL(t3, Py, Py),
502 MADD(t3, t3),
503 MMUL(t2, Px, t3),
504 MADD(t2, t2),
505 // Compute x' = m^2 - 2*s.
506 MMUL(Px, t1, t1),
507 MSUB(Px, t2),
508 MSUB(Px, t2),
509 // Compute z' = 2*y*z.
510 MMUL(t4, Py, Pz),
511 MSET(Pz, t4),
512 MADD(Pz, t4),
513 // Compute y' = m*(s - x') - 8*y^4. Note that we already have
514 // 2*y^2 in t3.
515 MSUB(t2, Px),
516 MMUL(Py, t1, t2),
517 MMUL(t4, t3, t3),
518 MSUB(Py, t4),
519 MSUB(Py, t4),
520 ENDCODE,
521 };
522 const _add = [_]u16{
523 // Compute u1 = x1*z2^2 (in t1) and s1 = y1*z2^3 (in t3).
524 MMUL(t3, P2z, P2z),
525 MMUL(t1, P1x, t3),
526 MMUL(t4, P2z, t3),
527 MMUL(t3, P1y, t4),
528 // Compute u2 = x2*z1^2 (in t2) and s2 = y2*z1^3 (in t4).
529 MMUL(t4, P1z, P1z),
530 MMUL(t2, P2x, t4),
531 MMUL(t5, P1z, t4),
532 MMUL(t4, P2y, t5),
533 //Compute h = u2 - u1 (in t2) and r = s2 - s1 (in t4).
534 MSUB(t2, t1),
535 MSUB(t4, t3),
536 // Report cases where r = 0 through the returned flag.
537 MTZ(t4),
538 // Compute u1*h^2 (in t6) and h^3 (in t5).
539 MMUL(t7, t2, t2),
540 MMUL(t6, t1, t7),
541 MMUL(t5, t7, t2),
542 // Compute x3 = r^2 - h^3 - 2*u1*h^2.
543 // t1 and t7 can be used as scratch registers.
544 MMUL(P1x, t4, t4),
545 MSUB(P1x, t5),
546 MSUB(P1x, t6),
547 MSUB(P1x, t6),
548 //Compute y3 = r*(u1*h^2 - x3) - s1*h^3.
549 MSUB(t6, P1x),
550 MMUL(P1y, t4, t6),
551 MMUL(t1, t5, t3),
552 MSUB(P1y, t1),
553 //Compute z3 = h*z1*z2.
554 MMUL(t1, P1z, P2z),
555 MMUL(P1z, t1, t2),
556 ENDCODE,
557 };
558 const affine = [_]u16{
559 // Save z*R in t1.
560 MSET(t1, P1z),
561 // Compute z^3 in t2.
562 MMUL(t2, P1z, P1z),
563 MMUL(t3, P1z, t2),
564 MMUL(t2, t3, P2z),
565 // Invert to (1/z^3) in t2.
566 MINV(t2, t3, t4),
567 // Compute y.
568 MSET(t3, P1y),
569 MMUL(P1y, t2, t3),
570 // Compute (1/z^2) in t3.
571 MMUL(t3, t2, t1),
572 // Compute x.
573 MSET(t2, P1x),
574 MMUL(P1x, t2, t3),
575 ENDCODE,
576 };
577 };
578
579 fn decode_mod(
580 comptime Curve: type,
581 x: *[jacobian_len(Curve)]u32,
582 src: [Curve.point_len / 2]u8,
583 ) u32 {
584 const mlen = comptime ((Curve.P[0] + 31) >> 5);
585 const tlen = comptime std.math.max(mlen << 2, Curve.point_len / 2) + 4;
586
587 var r: u32 = 0;
588 var pass: usize = 0;
589 while (pass < 2) : (pass += 1) {
590 var v: usize = 1;
591 var acc: u32 = 0;
592 var acc_len: u32 = 0;
593
594 var u: usize = 0;
595 while (u < tlen) : (u += 1) {
596 const b = if (u < Curve.point_len / 2)
597 @as(u32, src[Curve.point_len / 2 - 1 - u])
598 else
599 0;
600 acc |= b << @truncate(u5, acc_len);
601 acc_len += 8;
602 if (acc_len >= 31) {
603 const xw = acc & 0x7FFFFFFF;
604 acc_len -= 31;
605 acc = b >> @truncate(u5, 8 - acc_len);
606 if (v <= mlen) {
607 if (pass != 0) {
608 x[v] = r & xw;
609 } else {
610 const cc = @bitCast(u32, CMP(xw, Curve.P[v]));
611 r = MUX(EQ(cc, 0), r, cc);
612 }
613 } else if (pass == 0) {
614 r = MUX(EQ(xw, 0), r, 1);
615 }
616 v += 1;
617 }
618 }
619 r >>= 1;
620 r |= (r << 1);
621 }
622 x[0] = Curve.P[0];
623 return r & 1;
624 }
625
626 fn run_code(
627 comptime Curve: type,
628 P1: *Jacobian(Curve),
629 P2: Jacobian(Curve),
630 comptime Code: []const u16,
631 ) u32 {
632 comptime const jaclen = jacobian_len(Curve);
633
634 var t: [13][jaclen]u32 = undefined;
635 var result: u32 = 1;
636
637 t[0..3].* = P1.*;
638 t[3..6].* = P2;
639
640 comptime var u: usize = 0;
641 inline while (true) : (u += 1) {
642 comptime var op = Code[u];
643 if (op == 0)
644 break;
645 comptime const d = (op >> 8) & 0x0F;
646 comptime const a = (op >> 4) & 0x0F;
647 comptime const b = op & 0x0F;
648 op >>= 12;
649
650 switch (op) {
651 0 => t[d] = t[a],
652 1 => {
653 var ctl = add(jaclen, &t[d], t[a], 1);
654 ctl |= NOT(sub(jaclen, &t[d], Curve.P, 0));
655 _ = sub(jaclen, &t[d], Curve.P, ctl);
656 },
657 2 => _ = add(jaclen, &t[d], Curve.P, sub(jaclen, &t[d], t[a], 1)),
658 3 => montymul(Curve, &t[d], t[a], t[b], Curve.P, 1),
659 4 => {
660 var tp: [Curve.point_len / 2]u8 = undefined;
661 encode_jacobian_part(Curve, &tp, Curve.P);
662 tp[Curve.point_len / 2 - 1] -= 2;
663 modpow(Curve, &t[d], tp, 1, &t[a], &t[b]);
664 },
665 else => result &= ~iszero(jaclen, t[d]),
666 }
667 }
668 P1.* = t[0..3].*;
669 return result;
670 }
671
672 inline fn MUL31(x: u32, y: u32) u64 {
673 return @as(u64, x) * @as(u64, y);
674 }
675
676 inline fn MUL31_lo(x: u32, y: u32) u32 {
677 return (x *% y) & 0x7FFFFFFF;
678 }
679
680 inline fn MUX(ctl: u32, x: u32, y: u32) u32 {
681 return y ^ (@bitCast(u32, -@bitCast(i32, ctl)) & (x ^ y));
682 }
683 inline fn NOT(ctl: u32) u32 {
684 return ctl ^ 1;
685 }
686 inline fn NEQ(x: u32, y: u32) u32 {
687 const q = x ^ y;
688 return (q | @bitCast(u32, -@bitCast(i32, q))) >> 31;
689 }
690 inline fn EQ(x: u32, y: u32) u32 {
691 const q = x ^ y;
692 return NOT((q | @bitCast(u32, -@bitCast(i32, q))) >> 31);
693 }
694 inline fn CMP(x: u32, y: u32) i32 {
695 return @bitCast(i32, GT(x, y)) | -@bitCast(i32, GT(y, x));
696 }
697 inline fn GT(x: u32, y: u32) u32 {
698 const z = y -% x;
699 return (z ^ ((x ^ y) & (x ^ z))) >> 31;
700 }
701 inline fn LT(x: u32, y: u32) u32 {
702 return GT(y, x);
703 }
704 inline fn GE(x: u32, y: u32) u32 {
705 return NOT(GT(y, x));
706 }
707
708 fn CCOPY(ctl: u32, dst: []u8, src: []const u8) void {
709 for (src) |s, i| {
710 dst[i] = @truncate(u8, MUX(ctl, s, dst[i]));
711 }
712 }
713
714 // @TODO Remove lots of len and Curve parameters, just use the first byte calcualtions
715 // This will make all these functions shared for and reduce code bloat
716
717 inline fn set_zero(comptime len: usize, out: *[len]u32, bit_len: u32) void {
718 out[0] = bit_len;
719 mem.set(u32, out[1..][0 .. (bit_len + 31) >> 5], 0);
720 }
721
722 fn divrem(_hi: u32, _lo: u32, d: u32, r: *u32) u32 {
723 var hi = _hi;
724 var lo = _lo;
725 var q: u32 = 0;
726 const ch = EQ(hi, d);
727 hi = MUX(ch, 0, hi);
728
729 var k: u5 = 31;
730 while (k > 0) : (k -= 1) {
731 const j = @truncate(u5, 32 - @as(u6, k));
732 const w = (hi << j) | (lo >> k);
733 const ctl = GE(w, d) | (hi >> k);
734 const hi2 = (w -% d) >> j;
735 const lo2 = lo -% (d << k);
736 hi = MUX(ctl, hi2, hi);
737 lo = MUX(ctl, lo2, lo);
738 q |= ctl << k;
739 }
740 const cf = GE(lo, d) | hi;
741 q |= cf;
742 r.* = MUX(cf, lo -% d, lo);
743 return q;
744 }
745
746 inline fn div(hi: u32, lo: u32, d: u32) u32 {
747 var r: u32 = undefined;
748 return divrem(hi, lo, d, &r);
749 }
750
751 fn muladd_small(comptime len: usize, x: *[len]u32, z: u32, m: [len]u32) void {
752 var a0: u32 = undefined;
753 var a1: u32 = undefined;
754 var b0: u32 = undefined;
755 const mblr = @intCast(u5, m[0] & 31);
756 const mlen = (m[0] + 31) >> 5;
757 const hi = x[mlen];
758 if (mblr == 0) {
759 a0 = x[mlen];
760 mem.copyBackwards(u32, x[2..][0 .. mlen - 1], x[1..][0 .. mlen - 1]);
761 x[1] = z;
762 a1 = x[mlen];
763 b0 = m[mlen];
764 } else {
765 a0 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & 0x7FFFFFFF;
766 mem.copyBackwards(u32, x[2..][0 .. mlen - 1], x[1..][0 .. mlen - 1]);
767 x[1] = z;
768 a1 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & 0x7FFFFFFF;
769 b0 = ((m[mlen] << (31 - mblr)) | (m[mlen - 1] >> mblr)) & 0x7FFFFFFF;
770 }
771
772 const g = div(a0 >> 1, a1 | (a0 << 31), b0);
773 const q = MUX(EQ(a0, b0), 0x7FFFFFFF, MUX(EQ(g, 0), 0, g -% 1));
774
775 var cc: u32 = 0;
776 var tb: u32 = 1;
777 var u: usize = 1;
778 while (u <= mlen) : (u += 1) {
779 const mw = m[u];
780 const zl = MUL31(mw, q) + cc;
781 cc = @truncate(u32, zl >> 31);
782 const zw = @truncate(u32, zl) & 0x7FFFFFFF;
783 const xw = x[u];
784 var nxw = xw -% zw;
785 cc += nxw >> 31;
786 nxw &= 0x7FFFFFFF;
787 x[u] = nxw;
788 tb = MUX(EQ(nxw, mw), tb, GT(nxw, mw));
789 }
790
791 const over = GT(cc, hi);
792 const under = ~over & (tb | LT(cc, hi));
793 _ = add(len, x, m, over);
794 _ = sub(len, x, m, under);
795 }
796
797 fn to_monty(comptime len: usize, x: *[len]u32, m: [len]u32) void {
798 const mlen = (m[0] + 31) >> 5;
799 var k = mlen;
800 while (k > 0) : (k -= 1) {
801 muladd_small(len, x, 0, m);
802 }
803 }
804
805 fn modpow(
806 comptime Curve: type,
807 x: *[jacobian_len(Curve)]u32,
808 e: [Curve.point_len / 2]u8,
809 m0i: u32,
810 t1: *[jacobian_len(Curve)]u32,
811 t2: *[jacobian_len(Curve)]u32,
812 ) void {
813 comptime const jaclen = jacobian_len(Curve);
814 t1.* = x.*;
815 to_monty(jaclen, t1, Curve.P);
816 set_zero(jaclen, x, Curve.P[0]);
817 x[1] = 1;
818 comptime const bitlen = (Curve.point_len / 2) << 3;
819 var k: usize = 0;
820 while (k < bitlen) : (k += 1) {
821 const ctl = (e[Curve.point_len / 2 - 1 - (k >> 3)] >> (@truncate(u3, k & 7))) & 1;
822 montymul(Curve, t2, x.*, t1.*, Curve.P, m0i);
823 CCOPY(ctl, mem.asBytes(x), mem.asBytes(t2));
824 montymul(Curve, t2, t1.*, t1.*, Curve.P, m0i);
825 t1.* = t2.*;
826 }
827 }
828
829 fn encode_jacobian_part(comptime Curve: type, dst: *[Curve.point_len / 2]u8, x: [jacobian_len(Curve)]u32) void {
830 const xlen = (x[0] + 31) >> 5;
831
832 var buf = @ptrToInt(dst) + Curve.point_len / 2;
833 var len: usize = Curve.point_len / 2;
834 var k: usize = 1;
835 var acc: u32 = 0;
836 var acc_len: u5 = 0;
837 while (len != 0) {
838 const w = if (k <= xlen) x[k] else 0;
839 k += 1;
840 if (acc_len == 0) {
841 acc = w;
842 acc_len = 31;
843 } else {
844 const z = acc | (w << acc_len);
845 acc_len -= 1;
846 acc = w >> (31 - acc_len);
847 if (len >= 4) {
848 buf -= 4;
849 len -= 4;
850 mem.writeIntBig(u32, @intToPtr([*]u8, buf)[0..4], z);
851 } else {
852 switch (len) {
853 3 => {
854 @intToPtr(*u8, buf - 3).* = @truncate(u8, z >> 16);
855 @intToPtr(*u8, buf - 2).* = @truncate(u8, z >> 8);
856 },
857 2 => @intToPtr(*u8, buf - 2).* = @truncate(u8, z >> 8),
858 1 => {},
859 else => unreachable,
860 }
861 @intToPtr(*u8, buf - 1).* = @truncate(u8, z);
862 return;
863 }
864 }
865 }
866 }
867
868 fn montymul(
869 comptime Curve: type,
870 out: *[jacobian_len(Curve)]u32,
871 x: [jacobian_len(Curve)]u32,
872 y: [jacobian_len(Curve)]u32,
873 m: [jacobian_len(Curve)]u32,
874 m0i: u32,
875 ) void {
876 comptime const jaclen = jacobian_len(Curve);
877 const len = (m[0] + 31) >> 5;
878 const len4 = len & ~@as(usize, 3);
879 set_zero(jaclen, out, m[0]);
880 var dh: u32 = 0;
881 var u: usize = 0;
882 while (u < len) : (u += 1) {
883 const xu = x[u + 1];
884 const f = MUL31_lo(out[1] + MUL31_lo(x[u + 1], y[1]), m0i);
885
886 var r: u64 = 0;
887 var v: usize = 0;
888 while (v < len4) : (v += 4) {
889 comptime var j = 1;
890 inline while (j <= 4) : (j += 1) {
891 const z = out[v + j] +% MUL31(xu, y[v + j]) +% MUL31(f, m[v + j]) +% r;
892 r = z >> 31;
893 out[v + j - 1] = @truncate(u32, z) & 0x7FFFFFFF;
894 }
895 }
896 while (v < len) : (v += 1) {
897 const z = out[v + 1] +% MUL31(xu, y[v + 1]) +% MUL31(f, m[v + 1]) +% r;
898 r = z >> 31;
899 out[v] = @truncate(u32, z) & 0x7FFFFFFF;
900 }
901 dh += @truncate(u32, r);
902 out[len] = dh & 0x7FFFFFFF;
903 dh >>= 31;
904 }
905 out[0] = m[0];
906 const ctl = NEQ(dh, 0) | NOT(sub(jaclen, out, m, 0));
907 _ = sub(jaclen, out, m, ctl);
908 }
909
910 fn add(comptime len: usize, a: *[len]u32, b: [len]u32, ctl: u32) u32 {
911 var u: usize = 1;
912 var cc: u32 = 0;
913 while (u < len) : (u += 1) {
914 const aw = a[u];
915 const bw = b[u];
916 const naw = aw +% bw +% cc;
917 cc = naw >> 31;
918 a[u] = MUX(ctl, naw & 0x7FFFFFFF, aw);
919 }
920 return cc;
921 }
922
923 fn sub(comptime len: usize, a: *[len]u32, b: [len]u32, ctl: u32) u32 {
924 var cc: u32 = 0;
925 const m = (a[0] + 63) >> 5;
926 var u: usize = 1;
927 while (u < m) : (u += 1) {
928 const aw = a[u];
929 const bw = b[u];
930 const naw = aw -% bw -% cc;
931 cc = naw >> 31;
932 a[u] = MUX(ctl, naw & 0x7FFFFFFF, aw);
933 }
934 return cc;
935 }
936
937 fn iszero(comptime len: usize, arr: [len]u32) u32 {
938 var z: u32 = 0;
939 var u: usize = len - 1;
940 while (u > 0) : (u -= 1) {
941 z |= arr[u];
942 }
943 return ~(z | @bitCast(u32, -@bitCast(i32, z))) >> 31;
944 }
945};
946
947test "elliptic curve functions with secp384r1 curve" {
948 {
949 // Decode to Jacobian then encode again with no operations
950 var P: ecc.Jacobian(ecc.SECP384R1) = undefined;
951 var res: u32 = ecc.decode_to_jacobian(ecc.SECP384R1, &P, ecc.SECP384R1.base_point);
952 var out: [96]u8 = undefined;
953 ecc.encode_from_jacobian(ecc.SECP384R1, &out, P);
954 std.testing.expectEqual(ecc.SECP384R1.base_point, out);
955
956 // Multiply by one, check that the result is still the base point
957 mem.set(u8, &out, 0);
958 ecc.point_mul(ecc.SECP384R1, &P, &[1]u8{1});
959 ecc.encode_from_jacobian(ecc.SECP384R1, &out, P);
960 std.testing.expectEqual(ecc.SECP384R1.base_point, out);
961 }
962
963 {
964 // @TODO Remove this once std.crypto.rand works in .evented mode
965 var rand = blk: {
966 var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
967 try std.os.getrandom(&seed);
968 break :blk &std.rand.DefaultCsprng.init(seed).random;
969 };
970
971 // Derive a shared secret from a Diffie-Hellman key exchange
972 var seed: [48]u8 = undefined;
973 rand.bytes(&seed);
974 const kp1 = ecc.make_key_pair(ecc.SECP384R1, seed);
975 rand.bytes(&seed);
976 const kp2 = ecc.make_key_pair(ecc.SECP384R1, seed);
977
978 const shared1 = try ecc.scalarmult(ecc.SECP384R1, kp1.public_key, &kp2.secret_key);
979 const shared2 = try ecc.scalarmult(ecc.SECP384R1, kp2.public_key, &kp1.secret_key);
980 std.testing.expectEqual(shared1, shared2);
981 }
982
983 // @TODO Add tests with known points.
984}
libs/iguanatls/src/main.zig deleted-1843
......@@ -1,1843 +0,0 @@
1const std = @import("std");
2const mem = std.mem;
3const Allocator = mem.Allocator;
4const Sha384 = std.crypto.hash.sha2.Sha384;
5const Sha512 = std.crypto.hash.sha2.Sha512;
6const Sha256 = std.crypto.hash.sha2.Sha256;
7const Hmac256 = std.crypto.auth.hmac.sha2.HmacSha256;
8
9pub const asn1 = @import("asn1.zig");
10pub const x509 = @import("x509.zig");
11pub const crypto = @import("crypto.zig");
12
13const ciphers = @import("ciphersuites.zig");
14pub const ciphersuites = ciphers.suites;
15
16comptime {
17 std.testing.refAllDecls(x509);
18 std.testing.refAllDecls(asn1);
19 std.testing.refAllDecls(crypto);
20}
21
22fn handshake_record_length(reader: anytype) !usize {
23 return try record_length(0x16, reader);
24}
25
26pub const RecordTagLength = struct {
27 tag: u8,
28 length: u16,
29};
30pub fn record_tag_length(reader: anytype) !RecordTagLength {
31 const record_tag = try reader.readByte();
32
33 var record_header: [4]u8 = undefined;
34 try reader.readNoEof(&record_header);
35
36 if (!mem.eql(u8, record_header[0..2], "\x03\x03") and !mem.eql(u8, record_header[0..2], "\x03\x01"))
37 return error.ServerInvalidVersion;
38
39 const len = mem.readIntSliceBig(u16, record_header[2..4]);
40 return RecordTagLength{
41 .tag = record_tag,
42 .length = len,
43 };
44}
45
46pub fn record_length(t: u8, reader: anytype) !usize {
47 try check_record_type(t, reader);
48 var record_header: [4]u8 = undefined;
49 try reader.readNoEof(&record_header);
50 if (!mem.eql(u8, record_header[0..2], "\x03\x03") and !mem.eql(u8, record_header[0..2], "\x03\x01"))
51 return error.ServerInvalidVersion;
52 return mem.readIntSliceBig(u16, record_header[2..4]);
53}
54
55pub const ServerAlert = error{
56 AlertCloseNotify,
57 AlertUnexpectedMessage,
58 AlertBadRecordMAC,
59 AlertDecryptionFailed,
60 AlertRecordOverflow,
61 AlertDecompressionFailure,
62 AlertHandshakeFailure,
63 AlertNoCertificate,
64 AlertBadCertificate,
65 AlertUnsupportedCertificate,
66 AlertCertificateRevoked,
67 AlertCertificateExpired,
68 AlertCertificateUnknown,
69 AlertIllegalParameter,
70 AlertUnknownCA,
71 AlertAccessDenied,
72 AlertDecodeError,
73 AlertDecryptError,
74 AlertExportRestriction,
75 AlertProtocolVersion,
76 AlertInsufficientSecurity,
77 AlertInternalError,
78 AlertUserCanceled,
79 AlertNoRenegotiation,
80 AlertUnsupportedExtension,
81};
82
83fn check_record_type(
84 expected: u8,
85 reader: anytype,
86) (@TypeOf(reader).Error || ServerAlert || error{ ServerMalformedResponse, EndOfStream })!void {
87 const record_type = try reader.readByte();
88 // Alert
89 if (record_type == 0x15) {
90 // Skip SSL version, length of record
91 try reader.skipBytes(4, .{});
92
93 const severity = try reader.readByte();
94 const err_num = try reader.readByte();
95 return alert_byte_to_error(err_num);
96 }
97 if (record_type != expected)
98 return error.ServerMalformedResponse;
99}
100
101pub fn alert_byte_to_error(b: u8) (ServerAlert || error{ServerMalformedResponse}) {
102 return switch (b) {
103 0 => error.AlertCloseNotify,
104 10 => error.AlertUnexpectedMessage,
105 20 => error.AlertBadRecordMAC,
106 21 => error.AlertDecryptionFailed,
107 22 => error.AlertRecordOverflow,
108 30 => error.AlertDecompressionFailure,
109 40 => error.AlertHandshakeFailure,
110 41 => error.AlertNoCertificate,
111 42 => error.AlertBadCertificate,
112 43 => error.AlertUnsupportedCertificate,
113 44 => error.AlertCertificateRevoked,
114 45 => error.AlertCertificateExpired,
115 46 => error.AlertCertificateUnknown,
116 47 => error.AlertIllegalParameter,
117 48 => error.AlertUnknownCA,
118 49 => error.AlertAccessDenied,
119 50 => error.AlertDecodeError,
120 51 => error.AlertDecryptError,
121 60 => error.AlertExportRestriction,
122 70 => error.AlertProtocolVersion,
123 71 => error.AlertInsufficientSecurity,
124 80 => error.AlertInternalError,
125 90 => error.AlertUserCanceled,
126 100 => error.AlertNoRenegotiation,
127 110 => error.AlertUnsupportedExtension,
128 else => error.ServerMalformedResponse,
129 };
130}
131
132fn Sha256Reader(comptime Reader: anytype) type {
133 const State = struct {
134 sha256: *Sha256,
135 reader: Reader,
136 };
137 const S = struct {
138 pub fn read(state: State, buffer: []u8) Reader.Error!usize {
139 const amt = try state.reader.read(buffer);
140 if (amt != 0) {
141 state.sha256.update(buffer[0..amt]);
142 }
143 return amt;
144 }
145 };
146 return std.io.Reader(State, Reader.Error, S.read);
147}
148
149fn sha256_reader(sha256: *Sha256, reader: anytype) Sha256Reader(@TypeOf(reader)) {
150 return .{ .context = .{ .sha256 = sha256, .reader = reader } };
151}
152
153fn Sha256Writer(comptime Writer: anytype) type {
154 const State = struct {
155 sha256: *Sha256,
156 writer: Writer,
157 };
158 const S = struct {
159 pub fn write(state: State, buffer: []const u8) Writer.Error!usize {
160 const amt = try state.writer.write(buffer);
161 if (amt != 0) {
162 state.sha256.update(buffer[0..amt]);
163 }
164 return amt;
165 }
166 };
167 return std.io.Writer(State, Writer.Error, S.write);
168}
169
170fn sha256_writer(sha256: *Sha256, writer: anytype) Sha256Writer(@TypeOf(writer)) {
171 return .{ .context = .{ .sha256 = sha256, .writer = writer } };
172}
173
174fn CertificateReaderState(comptime Reader: type) type {
175 return struct {
176 reader: Reader,
177 length: usize,
178 idx: usize = 0,
179 };
180}
181
182fn CertificateReader(comptime Reader: type) type {
183 const S = struct {
184 pub fn read(state: *CertificateReaderState(Reader), buffer: []u8) Reader.Error!usize {
185 const out_bytes = std.math.min(buffer.len, state.length - state.idx);
186 const res = try state.reader.readAll(buffer[0..out_bytes]);
187 state.idx += res;
188 return res;
189 }
190 };
191
192 return std.io.Reader(*CertificateReaderState(Reader), Reader.Error, S.read);
193}
194
195pub const CertificateVerifier = union(enum) {
196 none,
197 function: anytype,
198 default,
199};
200
201pub fn CertificateVerifierReader(comptime Reader: type) type {
202 return CertificateReader(Sha256Reader(Reader));
203}
204
205pub fn ClientConnectError(comptime verifier: CertificateVerifier, comptime Reader: type, comptime Writer: type) type {
206 const Additional = error{
207 ServerInvalidVersion,
208 ServerMalformedResponse,
209 EndOfStream,
210 ServerInvalidCipherSuite,
211 ServerInvalidCompressionMethod,
212 ServerInvalidRenegotiationData,
213 ServerInvalidECPointCompression,
214 ServerInvalidProtocol,
215 ServerInvalidExtension,
216 ServerInvalidCurve,
217 ServerInvalidSignature,
218 ServerInvalidSignatureAlgorithm,
219 ServerAuthenticationFailed,
220 ServerInvalidVerifyData,
221 PreMasterGenerationFailed,
222 OutOfMemory,
223 };
224 const err_msg = "Certificate verifier function cannot be generic, use CertificateVerifierReader to get the reader argument type";
225 return Reader.Error || Writer.Error || ServerAlert || Additional || switch (verifier) {
226 .none => error{},
227 .function => |f| @typeInfo(@typeInfo(@TypeOf(f)).Fn.return_type orelse
228 @compileError(err_msg)).ErrorUnion.error_set || error{CertificateVerificationFailed},
229 .default => error{CertificateVerificationFailed},
230 };
231}
232
233// See http://howardhinnant.github.io/date_algorithms.html
234// Timestamp in seconds, only supports A.D. dates
235fn unix_timestamp_from_civil_date(year: u16, month: u8, day: u8) i64 {
236 var y: i64 = year;
237 if (month <= 2) y -= 1;
238 const era = @divTrunc(y, 400);
239 const yoe = y - era * 400; // [0, 399]
240 const doy = @divTrunc((153 * (month + (if (month > 2) @as(i64, -3) else 9)) + 2), 5) + day - 1; // [0, 365]
241 const doe = yoe * 365 + @divTrunc(yoe, 4) - @divTrunc(yoe, 100) + doy; // [0, 146096]
242 return (era * 146097 + doe - 719468) * 86400;
243}
244
245fn read_der_utc_timestamp(reader: anytype) !i64 {
246 var buf: [17]u8 = undefined;
247
248 const tag = try reader.readByte();
249 if (tag != 0x17)
250 return error.CertificateVerificationFailed;
251 const len = try asn1.der.parse_length(reader);
252 if (len > 17)
253 return error.CertificateVerificationFailed;
254
255 try reader.readNoEof(buf[0..len]);
256 const year = std.fmt.parseUnsigned(u16, buf[0..2], 10) catch
257 return error.CertificateVerificationFailed;
258 const month = std.fmt.parseUnsigned(u8, buf[2..4], 10) catch
259 return error.CertificateVerificationFailed;
260 const day = std.fmt.parseUnsigned(u8, buf[4..6], 10) catch
261 return error.CertificateVerificationFailed;
262
263 var time = unix_timestamp_from_civil_date(2000 + year, month, day);
264 time += (std.fmt.parseUnsigned(i64, buf[6..8], 10) catch
265 return error.CertificateVerificationFailed) * 3600;
266 time += (std.fmt.parseUnsigned(i64, buf[8..10], 10) catch
267 return error.CertificateVerificationFailed) * 60;
268
269 if (buf[len - 1] == 'Z') {
270 if (len == 13) {
271 time += std.fmt.parseUnsigned(u8, buf[10..12], 10) catch
272 return error.CertificateVerificationFailed;
273 } else if (len != 11) {
274 return error.CertificateVerificationFailed;
275 }
276 } else {
277 if (len == 15) {
278 if (buf[10] != '+' and buf[10] != '-')
279 return error.CertificateVerificationFailed;
280
281 var additional = (std.fmt.parseUnsigned(i64, buf[11..13], 10) catch
282 return error.CertificateVerificationFailed) * 3600;
283 additional += (std.fmt.parseUnsigned(i64, buf[13..15], 10) catch
284 return error.CertificateVerificationFailed) * 60;
285
286 time += if (buf[10] == '+') -additional else additional;
287 } else if (len == 17) {
288 if (buf[12] != '+' and buf[12] != '-')
289 return error.CertificateVerificationFailed;
290 time += std.fmt.parseUnsigned(u8, buf[10..12], 10) catch
291 return error.CertificateVerificationFailed;
292
293 var additional = (std.fmt.parseUnsigned(i64, buf[13..15], 10) catch
294 return error.CertificateVerificationFailed) * 3600;
295 additional += (std.fmt.parseUnsigned(i64, buf[15..17], 10) catch
296 return error.CertificateVerificationFailed) * 60;
297
298 time += if (buf[12] == '+') -additional else additional;
299 } else return error.CertificateVerificationFailed;
300 }
301 return time;
302}
303
304fn check_cert_timestamp(time: i64, tag_byte: u8, length: usize, reader: anytype) !void {
305 if (time < (try read_der_utc_timestamp(reader)))
306 return error.CertificateVerificationFailed;
307 if (time > (try read_der_utc_timestamp(reader)))
308 return error.CertificateVerificationFailed;
309}
310
311fn add_dn_field(state: *VerifierCaptureState, tag: u8, length: usize, reader: anytype) !void {
312 const seq_tag = try reader.readByte();
313 if (seq_tag != 0x30)
314 return error.CertificateVerificationFailed;
315 const seq_length = try asn1.der.parse_length(reader);
316
317 const oid_tag = try reader.readByte();
318 if (oid_tag != 0x06)
319 return error.CertificateVerificationFailed;
320
321 const oid_length = try asn1.der.parse_length(reader);
322 if (oid_length == 3 and (try reader.isBytes("\x55\x04\x03"))) {
323 // Common name
324 const common_name_tag = try reader.readByte();
325 if (common_name_tag != 0x04 and common_name_tag != 0x0c and common_name_tag != 0x13 and common_name_tag != 0x16)
326 return error.CertificateVerificationFailed;
327 const common_name_len = try asn1.der.parse_length(reader);
328 state.list.items[state.list.items.len - 1].common_name = state.fbs.buffer[state.fbs.pos .. state.fbs.pos + common_name_len];
329 }
330}
331
332fn add_cert_subject_dn(state: *VerifierCaptureState, tag: u8, length: usize, reader: anytype) !void {
333 state.list.items[state.list.items.len - 1].dn = state.fbs.buffer[state.fbs.pos .. state.fbs.pos + length];
334 const schema = .{
335 .sequence_of,
336 .{
337 .capture, 0, .set,
338 },
339 };
340 const captures = .{
341 state, add_dn_field,
342 };
343 try asn1.der.parse_schema_tag_len(tag, length, schema, captures, reader);
344}
345
346fn add_cert_public_key(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void {
347 state.list.items[state.list.items.len - 1].public_key = x509.parse_public_key(
348 state.allocator,
349 reader,
350 ) catch |err| switch (err) {
351 error.MalformedDER => return error.CertificateVerificationFailed,
352 else => |e| return e,
353 };
354}
355
356fn add_server_cert(state: *VerifierCaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
357 const is_ca = state.list.items.len != 0;
358
359 const encoded_length = asn1.der.encode_length(length).slice();
360 // This is not errdefered since default_cert_verifier call takes care of cleaning up all the certificate data.
361 // Same for the signature.data
362 const cert_bytes = try state.allocator.alloc(u8, length + 1 + encoded_length.len);
363 cert_bytes[0] = tag_byte;
364 mem.copy(u8, cert_bytes[1 .. 1 + encoded_length.len], encoded_length);
365
366 try reader.readNoEof(cert_bytes[1 + encoded_length.len ..]);
367 (try state.list.addOne(state.allocator)).* = .{
368 .is_ca = is_ca,
369 .bytes = cert_bytes,
370 .dn = undefined,
371 .common_name = &[0]u8{},
372 .public_key = x509.PublicKey.empty,
373 .signature = asn1.BitString{ .data = &[0]u8{}, .bit_len = 0 },
374 .signature_algorithm = undefined,
375 };
376
377 const schema = .{
378 .sequence,
379 .{
380 .{ .context_specific, 0 }, // version
381 .{.int}, // serialNumber
382 .{.sequence}, // signature
383 .{.sequence}, // issuer
384 .{ .capture, 0, .sequence }, // validity
385 .{ .capture, 1, .sequence }, // subject
386 .{ .capture, 2, .sequence }, // subjectPublicKeyInfo
387 .{ .optional, .context_specific, 1 }, // issuerUniqueID
388 .{ .optional, .context_specific, 2 }, // subjectUniqueID
389 .{ .optional, .context_specific, 3 }, // extensions
390 },
391 };
392
393 const captures = .{
394 std.time.timestamp(), check_cert_timestamp,
395 state, add_cert_subject_dn,
396 state, add_cert_public_key,
397 };
398
399 var fbs = std.io.fixedBufferStream(@as([]const u8, cert_bytes[1 + encoded_length.len ..]));
400 state.fbs = &fbs;
401
402 asn1.der.parse_schema_tag_len(tag_byte, length, schema, captures, fbs.reader()) catch |err| switch (err) {
403 error.InvalidLength,
404 error.InvalidTag,
405 error.InvalidContainerLength,
406 error.DoesNotMatchSchema,
407 => return error.CertificateVerificationFailed,
408 else => |e| return e,
409 };
410}
411
412fn set_signature_algorithm(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void {
413 const oid_tag = try reader.readByte();
414 if (oid_tag != 0x06)
415 return error.CertificateVerificationFailed;
416
417 const oid_length = try asn1.der.parse_length(reader);
418 if (oid_length == 9) {
419 var oid_bytes: [9]u8 = undefined;
420 try reader.readNoEof(&oid_bytes);
421
422 const cert = &state.list.items[state.list.items.len - 1];
423 if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 })) {
424 cert.signature_algorithm = .rsa;
425 } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 })) {
426 cert.signature_algorithm = .rsa_md5;
427 } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 })) {
428 cert.signature_algorithm = .rsa_sha1;
429 } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B })) {
430 cert.signature_algorithm = .rsa_sha256;
431 } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C })) {
432 cert.signature_algorithm = .rsa_sha384;
433 } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D })) {
434 cert.signature_algorithm = .rsa_sha512;
435 } else {
436 return error.CertificateVerificationFailed;
437 }
438 return;
439 } else if (oid_length == 10) {
440 // @TODO
441 // ECDSA + <Hash> algorithms
442 }
443
444 return error.CertificateVerificationFailed;
445}
446
447fn set_signature_value(state: *VerifierCaptureState, tag: u8, length: usize, reader: anytype) !void {
448 const unused_bits = try reader.readByte();
449 const bit_count = (length - 1) * 8 - unused_bits;
450 const signature_bytes = try state.allocator.alloc(u8, length - 1);
451 errdefer state.allocator.free(signature_bytes);
452 try reader.readNoEof(signature_bytes);
453 state.list.items[state.list.items.len - 1].signature = .{
454 .data = signature_bytes,
455 .bit_len = bit_count,
456 };
457}
458
459fn verify_signature(
460 allocator: *Allocator,
461 signature_algorithm: SignatureAlgorithm,
462 signature: asn1.BitString,
463 hash: []const u8,
464 public_key: x509.PublicKey,
465) !bool {
466 // @TODO ECDSA algorithms
467 if (public_key != .rsa) return false;
468 const prefix: []const u8 = switch (signature_algorithm) {
469 // Deprecated hash algos
470 .rsa_md5, .rsa_sha1 => return false,
471 // @TODO How does this one work?
472 .rsa => return false,
473 .rsa_sha256 => &[_]u8{
474 0x30, 0x31, 0x30, 0x0d, 0x06,
475 0x09, 0x60, 0x86, 0x48, 0x01,
476 0x65, 0x03, 0x04, 0x02, 0x01,
477 0x05, 0x00, 0x04, 0x20,
478 },
479 .rsa_sha384 => &[_]u8{
480 0x30, 0x41, 0x30, 0x0d, 0x06,
481 0x09, 0x60, 0x86, 0x48, 0x01,
482 0x65, 0x03, 0x04, 0x02, 0x02,
483 0x05, 0x00, 0x04, 0x30,
484 },
485 .rsa_sha512 => &[_]u8{
486 0x30, 0x51, 0x30, 0x0d, 0x06,
487 0x09, 0x60, 0x86, 0x48, 0x01,
488 0x65, 0x03, 0x04, 0x02, 0x03,
489 0x05, 0x00, 0x04, 0x40,
490 },
491 };
492
493 // RSA hash verification with PKCS 1 V1_5 padding
494 const modulus = std.math.big.int.Const{ .limbs = public_key.rsa.modulus, .positive = true };
495 const exponent = std.math.big.int.Const{ .limbs = public_key.rsa.exponent, .positive = true };
496 if (modulus.bitCountAbs() != signature.bit_len)
497 return false;
498
499 // encrypt the signature using the RSA key
500 // @TODO better algorithm, this is probably slow as hell
501 var encrypted_signature = try std.math.big.int.Managed.initSet(allocator, @as(usize, 1));
502 defer encrypted_signature.deinit();
503
504 {
505 var curr_exponent = try exponent.toManaged(allocator);
506 defer curr_exponent.deinit();
507
508 const curr_base_limbs = try allocator.alloc(
509 usize,
510 std.math.divCeil(usize, signature.data.len, @sizeOf(usize)) catch unreachable,
511 );
512 const curr_base_limb_bytes = @ptrCast([*]u8, curr_base_limbs)[0..signature.data.len];
513 mem.copy(u8, curr_base_limb_bytes, signature.data);
514 mem.reverse(u8, curr_base_limb_bytes);
515 var curr_base = (std.math.big.int.Mutable{
516 .limbs = curr_base_limbs,
517 .positive = true,
518 .len = curr_base_limbs.len,
519 }).toManaged(allocator);
520 defer curr_base.deinit();
521
522 // encrypted = signature ^ key.exponent MOD key.modulus
523 while (curr_exponent.toConst().orderAgainstScalar(0) == .gt) {
524 if (curr_exponent.isOdd()) {
525 try encrypted_signature.ensureMulCapacity(encrypted_signature.toConst(), curr_base.toConst());
526 try encrypted_signature.mul(encrypted_signature.toConst(), curr_base.toConst());
527 try llmod(&encrypted_signature, modulus);
528 }
529 try curr_base.sqr(curr_base.toConst());
530 try llmod(&curr_base, modulus);
531 try curr_exponent.shiftRight(curr_exponent, 1);
532 }
533 }
534 // EMSA-PKCS1-V1_5-ENCODE
535 if (encrypted_signature.limbs.len * @sizeOf(usize) < signature.data.len)
536 return false;
537
538 const enc_buf = @ptrCast([*]u8, encrypted_signature.limbs.ptr)[0..signature.data.len];
539 mem.reverse(u8, enc_buf);
540
541 if (enc_buf[0] != 0x00 or enc_buf[1] != 0x01)
542 return false;
543 if (!mem.endsWith(u8, enc_buf, hash))
544 return false;
545 if (!mem.endsWith(u8, enc_buf[0 .. enc_buf.len - hash.len], prefix))
546 return false;
547 if (enc_buf[enc_buf.len - hash.len - prefix.len - 1] != 0x00)
548 return false;
549 for (enc_buf[2 .. enc_buf.len - hash.len - prefix.len - 1]) |c| {
550 if (c != 0xff) return false;
551 }
552
553 return true;
554}
555
556fn certificate_verify_signature(
557 allocator: *Allocator,
558 signature_algorithm: SignatureAlgorithm,
559 signature: asn1.BitString,
560 bytes: []const u8,
561 public_key: x509.PublicKey,
562) !bool {
563 // @TODO ECDSA algorithms
564 if (public_key != .rsa) return false;
565
566 var hash_buf: [64]u8 = undefined;
567 var hash: []u8 = undefined;
568
569 switch (signature_algorithm) {
570 // Deprecated hash algos
571 .rsa_md5, .rsa_sha1 => return false,
572 // @TODO How does this one work?
573 .rsa => return false,
574
575 .rsa_sha256 => {
576 Sha256.hash(bytes, hash_buf[0..32], .{});
577 hash = hash_buf[0..32];
578 },
579 .rsa_sha384 => {
580 Sha384.hash(bytes, hash_buf[0..48], .{});
581 hash = hash_buf[0..48];
582 },
583 .rsa_sha512 => {
584 Sha512.hash(bytes, hash_buf[0..64], .{});
585 hash = &hash_buf;
586 },
587 }
588 return try verify_signature(allocator, signature_algorithm, signature, hash, public_key);
589}
590
591// res = res mod N
592fn llmod(res: *std.math.big.int.Managed, n: std.math.big.int.Const) !void {
593 var temp = try std.math.big.int.Managed.init(res.allocator);
594 defer temp.deinit();
595 try temp.divTrunc(res, res.toConst(), n);
596}
597
598const SignatureAlgorithm = enum {
599 rsa,
600 rsa_md5,
601 rsa_sha1,
602 rsa_sha256,
603 rsa_sha384,
604 rsa_sha512,
605 // @TODO ECDSA versions
606};
607
608const ServerCertificate = struct {
609 bytes: []const u8,
610 dn: []const u8,
611 common_name: []const u8,
612 public_key: x509.PublicKey,
613 signature: asn1.BitString,
614 signature_algorithm: SignatureAlgorithm,
615 is_ca: bool,
616};
617
618const VerifierCaptureState = struct {
619 list: std.ArrayListUnmanaged(ServerCertificate),
620 allocator: *Allocator,
621 // Used in `add_server_cert` to avoid an extra allocation
622 fbs: *std.io.FixedBufferStream([]const u8),
623};
624
625// @TODO Move out of here
626const ReverseSplitIterator = struct {
627 buffer: []const u8,
628 index: ?usize,
629 delimiter: []const u8,
630
631 pub fn next(self: *ReverseSplitIterator) ?[]const u8 {
632 const end = self.index orelse return null;
633 const start = if (mem.lastIndexOfLinear(u8, self.buffer[0..end], self.delimiter)) |delim_start| blk: {
634 self.index = delim_start;
635 break :blk delim_start + self.delimiter.len;
636 } else blk: {
637 self.index = null;
638 break :blk 0;
639 };
640 return self.buffer[start..end];
641 }
642};
643
644fn reverse_split(buffer: []const u8, delimiter: []const u8) ReverseSplitIterator {
645 std.debug.assert(delimiter.len != 0);
646 return .{
647 .index = buffer.len,
648 .buffer = buffer,
649 .delimiter = delimiter,
650 };
651}
652
653pub fn default_cert_verifier(
654 allocator: *mem.Allocator,
655 reader: anytype,
656 certs_bytes: usize,
657 trusted_certificates: []const x509.TrustAnchor,
658 hostname: []const u8,
659) !x509.PublicKey {
660 var capture_state = VerifierCaptureState{
661 .list = try std.ArrayListUnmanaged(ServerCertificate).initCapacity(allocator, 3),
662 .allocator = allocator,
663 .fbs = undefined,
664 };
665 defer {
666 for (capture_state.list.items) |cert| {
667 cert.public_key.deinit(allocator);
668 allocator.free(cert.bytes);
669 allocator.free(cert.signature.data);
670 }
671 capture_state.list.deinit(allocator);
672 }
673
674 const schema = .{
675 .sequence, .{
676 // tbsCertificate
677 .{ .capture, 0, .sequence },
678 // signatureAlgorithm
679 .{ .capture, 1, .sequence },
680 // signatureValue
681 .{ .capture, 2, .bit_string },
682 },
683 };
684 const captures = .{
685 &capture_state, add_server_cert,
686 &capture_state, set_signature_algorithm,
687 &capture_state, set_signature_value,
688 };
689
690 var bytes_read: u24 = 0;
691 while (bytes_read < certs_bytes) {
692 const cert_length = try reader.readIntBig(u24);
693
694 asn1.der.parse_schema(schema, captures, reader) catch |err| switch (err) {
695 error.InvalidLength,
696 error.InvalidTag,
697 error.InvalidContainerLength,
698 error.DoesNotMatchSchema,
699 => return error.CertificateVerificationFailed,
700 else => |e| return e,
701 };
702
703 bytes_read += 3 + cert_length;
704 }
705 if (bytes_read != certs_bytes)
706 return error.CertificateVerificationFailed;
707
708 const chain = capture_state.list.items;
709 if (chain.len == 0) return error.CertificateVerificationFailed;
710 // Check if the hostname matches the leaf certificate's common name
711 {
712 var common_name_split = reverse_split(chain[0].common_name, ".");
713 var hostname_split = reverse_split(hostname, ".");
714 while (true) {
715 const cn_part = common_name_split.next();
716 const hn_part = hostname_split.next();
717
718 if (cn_part) |cnp| {
719 if (hn_part == null and common_name_split.index == null and mem.eql(u8, cnp, "www"))
720 break
721 else if (hn_part) |hnp| {
722 if (mem.eql(u8, cnp, "*"))
723 continue;
724 if (!mem.eql(u8, cnp, hnp))
725 return error.CertificateVerificationFailed;
726 }
727 } else if (hn_part != null)
728 return error.CertificateVerificationFailed
729 else
730 break;
731 }
732 }
733
734 var i: usize = 0;
735 while (i < chain.len - 1) : (i += 1) {
736 if (!try certificate_verify_signature(
737 allocator,
738 chain[i].signature_algorithm,
739 chain[i].signature,
740 chain[i].bytes,
741 chain[i + 1].public_key,
742 )) {
743 return error.CertificateVerificationFailed;
744 }
745 }
746
747 for (chain) |cert| {
748 for (trusted_certificates) |trusted| {
749 // Try to find an exact match to a trusted certificate
750 if (cert.is_ca == trusted.is_ca and mem.eql(u8, cert.dn, trusted.dn) and
751 cert.public_key.eql(trusted.public_key))
752 {
753 const key = chain[0].public_key;
754 chain[0].public_key = x509.PublicKey.empty;
755 return key;
756 }
757
758 if (!trusted.is_ca)
759 continue;
760
761 if (try certificate_verify_signature(
762 allocator,
763 cert.signature_algorithm,
764 cert.signature,
765 cert.bytes,
766 trusted.public_key,
767 )) {
768 const key = chain[0].public_key;
769 chain[0].public_key = x509.PublicKey.empty;
770 return key;
771 }
772 }
773 }
774 return error.CertificateVerificationFailed;
775}
776
777pub fn extract_cert_public_key(allocator: *Allocator, reader: anytype, length: usize) !x509.PublicKey {
778 const CaptureState = struct {
779 pub_key: x509.PublicKey,
780 allocator: *Allocator,
781 };
782 var capture_state = CaptureState{
783 .pub_key = undefined,
784 .allocator = allocator,
785 };
786
787 var pub_key: x509.PublicKey = undefined;
788 const schema = .{
789 .sequence, .{
790 // tbsCertificate
791 .{
792 .sequence,
793 .{
794 .{ .context_specific, 0 }, // version
795 .{.int}, // serialNumber
796 .{.sequence}, // signature
797 .{.sequence}, // issuer
798 .{.sequence}, // validity
799 .{.sequence}, // subject
800 .{ .capture, 0, .sequence }, // subjectPublicKeyInfo
801 .{ .optional, .context_specific, 1 }, // issuerUniqueID
802 .{ .optional, .context_specific, 2 }, // subjectUniqueID
803 .{ .optional, .context_specific, 3 }, // extensions
804 },
805 },
806 // signatureAlgorithm
807 .{.sequence},
808 // signatureValue
809 .{.bit_string},
810 },
811 };
812 const captures = .{
813 &capture_state, struct {
814 fn f(state: *CaptureState, tag: u8, _: usize, subreader: anytype) !void {
815 state.pub_key = x509.parse_public_key(state.allocator, subreader) catch |err| switch (err) {
816 error.MalformedDER => return error.ServerMalformedResponse,
817 else => |e| return e,
818 };
819 }
820 }.f,
821 };
822
823 const cert_length = try reader.readIntBig(u24);
824 asn1.der.parse_schema(schema, captures, reader) catch |err| switch (err) {
825 error.InvalidLength,
826 error.InvalidTag,
827 error.InvalidContainerLength,
828 error.DoesNotMatchSchema,
829 => return error.ServerMalformedResponse,
830 else => |e| return e,
831 };
832 errdefer capture_state.pub_key.deinit(allocator);
833
834 try reader.skipBytes(length - cert_length - 3, .{});
835 return capture_state.pub_key;
836}
837
838pub const curves = struct {
839 pub const x25519 = struct {
840 pub const name = "x25519";
841 const tag = 0x001D;
842 const pub_key_len = 32;
843 const Keys = std.crypto.dh.X25519.KeyPair;
844
845 inline fn make_key_pair(rand: *std.rand.Random) Keys {
846 while (true) {
847 var seed: [32]u8 = undefined;
848 rand.bytes(&seed);
849 return std.crypto.dh.X25519.KeyPair.create(seed) catch continue;
850 } else unreachable;
851 }
852
853 inline fn make_pre_master_secret(
854 key_pair: Keys,
855 pre_master_secret_buf: []u8,
856 server_public_key: *const [32]u8,
857 ) ![]const u8 {
858 pre_master_secret_buf[0..32].* = std.crypto.dh.X25519.scalarmult(
859 key_pair.secret_key,
860 server_public_key.*,
861 ) catch return error.PreMasterGenerationFailed;
862 return pre_master_secret_buf[0..32];
863 }
864 };
865
866 pub const secp384r1 = struct {
867 pub const name = "secp384r1";
868 const tag = 0x0018;
869 const pub_key_len = 97;
870 const Keys = crypto.ecc.KeyPair(crypto.ecc.SECP384R1);
871
872 inline fn make_key_pair(rand: *std.rand.Random) Keys {
873 var seed: [48]u8 = undefined;
874 rand.bytes(&seed);
875 return crypto.ecc.make_key_pair(crypto.ecc.SECP384R1, seed);
876 }
877
878 inline fn make_pre_master_secret(
879 key_pair: Keys,
880 pre_master_secret_buf: []u8,
881 server_public_key: *const [97]u8,
882 ) ![]const u8 {
883 pre_master_secret_buf[0..96].* = crypto.ecc.scalarmult(
884 crypto.ecc.SECP384R1,
885 server_public_key[1..].*,
886 &key_pair.secret_key,
887 ) catch return error.PreMasterGenerationFailed;
888 return pre_master_secret_buf[0..48];
889 }
890 };
891
892 pub const secp256r1 = struct {
893 pub const name = "secp256r1";
894 const tag = 0x0017;
895 const pub_key_len = 65;
896 const Keys = crypto.ecc.KeyPair(crypto.ecc.SECP256R1);
897
898 inline fn make_key_pair(rand: *std.rand.Random) Keys {
899 var seed: [32]u8 = undefined;
900 rand.bytes(&seed);
901 return crypto.ecc.make_key_pair(crypto.ecc.SECP256R1, seed);
902 }
903
904 inline fn make_pre_master_secret(
905 key_pair: Keys,
906 pre_master_secret_buf: []u8,
907 server_public_key: *const [65]u8,
908 ) ![]const u8 {
909 pre_master_secret_buf[0..64].* = crypto.ecc.scalarmult(
910 crypto.ecc.SECP256R1,
911 server_public_key[1..].*,
912 &key_pair.secret_key,
913 ) catch return error.PreMasterGenerationFailed;
914 return pre_master_secret_buf[0..32];
915 }
916 };
917
918 pub const all = &[_]type{ x25519, secp384r1, secp256r1 };
919
920 fn max_pub_key_len(comptime list: anytype) usize {
921 var max: usize = 0;
922 for (list) |curve| {
923 if (curve.pub_key_len > max)
924 max = curve.pub_key_len;
925 }
926 return max;
927 }
928
929 fn max_pre_master_secret_len(comptime list: anytype) usize {
930 var max: usize = 0;
931 for (list) |curve| {
932 const curr = @typeInfo(std.meta.fieldInfo(curve.Keys, .public_key).field_type).Array.len;
933 if (curr > max)
934 max = curr;
935 }
936 return max;
937 }
938
939 fn KeyPair(comptime list: anytype) type {
940 var fields: [list.len]std.builtin.TypeInfo.UnionField = undefined;
941 for (list) |curve, i| {
942 fields[i] = .{
943 .name = curve.name,
944 .field_type = curve.Keys,
945 .alignment = @alignOf(curve.Keys),
946 };
947 }
948 return @Type(.{
949 .Union = .{
950 .layout = .Extern,
951 .tag_type = null,
952 .fields = &fields,
953 .decls = &[0]std.builtin.TypeInfo.Declaration{},
954 },
955 });
956 }
957
958 inline fn make_key_pair(comptime list: anytype, curve_id: u16, rand: *std.rand.Random) KeyPair(list) {
959 inline for (list) |curve| {
960 if (curve.tag == curve_id) {
961 return @unionInit(KeyPair(list), curve.name, curve.make_key_pair(rand));
962 }
963 }
964 unreachable;
965 }
966
967 inline fn make_pre_master_secret(
968 comptime list: anytype,
969 curve_id: u16,
970 key_pair: KeyPair(list),
971 pre_master_secret_buf: *[max_pre_master_secret_len(list)]u8,
972 server_public_key: [max_pub_key_len(list)]u8,
973 ) ![]const u8 {
974 inline for (list) |curve| {
975 if (curve.tag == curve_id) {
976 return try curve.make_pre_master_secret(
977 @field(key_pair, curve.name),
978 pre_master_secret_buf,
979 server_public_key[0..curve.pub_key_len],
980 );
981 }
982 }
983 unreachable;
984 }
985};
986
987pub fn client_connect(
988 options: anytype,
989 hostname: []const u8,
990) ClientConnectError(
991 options.cert_verifier,
992 @TypeOf(options.reader),
993 @TypeOf(options.writer),
994)!Client(
995 @TypeOf(options.reader),
996 @TypeOf(options.writer),
997 if (@hasField(@TypeOf(options), "ciphersuites"))
998 options.ciphersuites
999 else
1000 ciphersuites.all,
1001 @hasField(@TypeOf(options), "protocols"),
1002) {
1003 const Options = @TypeOf(options);
1004 if (@TypeOf(options.cert_verifier) != CertificateVerifier and
1005 @TypeOf(options.cert_verifier) != @Type(.EnumLiteral))
1006 @compileError("cert_verifier should be of type CertificateVerifier");
1007
1008 if (!@hasField(Options, "temp_allocator"))
1009 @compileError("Option tuple is missing field 'temp_allocator'");
1010 if (options.cert_verifier == .default) {
1011 if (!@hasField(Options, "trusted_certificates"))
1012 @compileError("Option tuple is missing field 'trusted_certificates' for .default cert_verifier");
1013 }
1014
1015 const suites = if (!@hasField(Options, "ciphersuites"))
1016 ciphersuites.all
1017 else
1018 options.ciphersuites;
1019 if (suites.len == 0)
1020 @compileError("Must provide at least one ciphersuite type.");
1021
1022 const curvelist = if (!@hasField(Options, "curves"))
1023 curves.all
1024 else
1025 options.curves;
1026 if (curvelist.len == 0)
1027 @compileError("Must provide at least one curve type.");
1028
1029 const has_alpn = comptime @hasField(Options, "protocols");
1030 var handshake_record_hash = Sha256.init(.{});
1031 const reader = options.reader;
1032 const writer = options.writer;
1033 const hashing_reader = sha256_reader(&handshake_record_hash, reader);
1034 const hashing_writer = sha256_writer(&handshake_record_hash, writer);
1035
1036 var client_random: [32]u8 = undefined;
1037 const rand = if (!@hasField(Options, "rand"))
1038 std.crypto.random
1039 else
1040 options.rand;
1041
1042 rand.bytes(&client_random);
1043
1044 var server_random: [32]u8 = undefined;
1045 const ciphersuite_bytes = 2 * suites.len + 2;
1046 const alpn_bytes = if (has_alpn) blk: {
1047 var sum: usize = 0;
1048 for (options.protocols) |proto| {
1049 sum += proto.len;
1050 }
1051 break :blk 6 + options.protocols.len + sum;
1052 } else 0;
1053 const curvelist_bytes = 2 * curvelist.len;
1054 var protocol: if (has_alpn) []const u8 else void = undefined;
1055 {
1056 const client_hello_start = comptime blk: {
1057 // TODO: We assume the compiler is running in a little endian system
1058 var starting_part: [46]u8 = [_]u8{
1059 // Record header: Handshake record type, protocol version, handshake size
1060 0x16, 0x03, 0x01, undefined, undefined,
1061 // Handshake message type, bytes of client hello
1062 0x01, undefined, undefined, undefined,
1063 // Client version (hardcoded to TLS 1.2 even for TLS 1.3)
1064 0x03,
1065 0x03,
1066 } ++ ([1]u8{undefined} ** 32) ++ [_]u8{
1067 // Session ID
1068 0x00,
1069 } ++ mem.toBytes(@byteSwap(u16, ciphersuite_bytes));
1070 // using .* = mem.asBytes(...).* or mem.writeIntBig didn't work...
1071
1072 // Same as above, couldnt achieve this with a single buffer.
1073 // TLS_EMPTY_RENEGOTIATION_INFO_SCSV
1074 var ciphersuite_buf: []const u8 = &[2]u8{ 0x00, 0x0f };
1075 for (suites) |cs, i| {
1076 // Also check for properties of the ciphersuites here
1077 if (cs.key_exchange != .ecdhe)
1078 @compileError("Non ECDHE key exchange is not supported yet.");
1079 if (cs.hash != .sha256)
1080 @compileError("Non SHA256 hash algorithm is not supported yet.");
1081
1082 ciphersuite_buf = ciphersuite_buf ++ mem.toBytes(@byteSwap(u16, cs.tag));
1083 }
1084
1085 var ending_part: [13]u8 = [_]u8{
1086 // Compression methods (no compression)
1087 0x01, 0x00,
1088 // Extensions length
1089 undefined, undefined,
1090 // Extension: server name
1091 // id, length, length of entry
1092 0x00, 0x00,
1093 undefined, undefined,
1094 undefined, undefined,
1095 // entry type, length of bytes
1096 0x00, undefined,
1097 undefined,
1098 };
1099 break :blk starting_part ++ ciphersuite_buf ++ ending_part;
1100 };
1101
1102 var msg_buf = client_hello_start.ptr[0..client_hello_start.len].*;
1103 mem.writeIntBig(u16, msg_buf[3..5], @intCast(u16, alpn_bytes + hostname.len + 0x55 + ciphersuite_bytes + curvelist_bytes));
1104 mem.writeIntBig(u24, msg_buf[6..9], @intCast(u24, alpn_bytes + hostname.len + 0x51 + ciphersuite_bytes + curvelist_bytes));
1105 mem.copy(u8, msg_buf[11..43], &client_random);
1106 mem.writeIntBig(u16, msg_buf[48 + ciphersuite_bytes ..][0..2], @intCast(u16, alpn_bytes + hostname.len + 0x28 + curvelist_bytes));
1107 mem.writeIntBig(u16, msg_buf[52 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 5));
1108 mem.writeIntBig(u16, msg_buf[54 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 3));
1109 mem.writeIntBig(u16, msg_buf[57 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len));
1110 try writer.writeAll(msg_buf[0..5]);
1111 try hashing_writer.writeAll(msg_buf[5..]);
1112 }
1113 try hashing_writer.writeAll(hostname);
1114 if (has_alpn) {
1115 var msg_buf = [6]u8{ 0x00, 0x10, undefined, undefined, undefined, undefined };
1116 mem.writeIntBig(u16, msg_buf[2..4], @intCast(u16, alpn_bytes - 4));
1117 mem.writeIntBig(u16, msg_buf[4..6], @intCast(u16, alpn_bytes - 6));
1118 try hashing_writer.writeAll(&msg_buf);
1119 for (options.protocols) |proto| {
1120 try hashing_writer.writeByte(@intCast(u8, proto.len));
1121 try hashing_writer.writeAll(proto);
1122 }
1123 }
1124
1125 // Extension: supported groups
1126 {
1127 var msg_buf = [6]u8{
1128 0x00, 0x0A,
1129 undefined, undefined,
1130 undefined, undefined,
1131 };
1132
1133 mem.writeIntBig(u16, msg_buf[2..4], @intCast(u16, curvelist_bytes + 2));
1134 mem.writeIntBig(u16, msg_buf[4..6], @intCast(u16, curvelist_bytes));
1135 try hashing_writer.writeAll(&msg_buf);
1136
1137 inline for (curvelist) |curve| {
1138 try hashing_writer.writeIntBig(u16, curve.tag);
1139 }
1140 }
1141
1142 try hashing_writer.writeAll(&[25]u8{
1143 // Extension: EC point formats => uncompressed point format
1144 0x00, 0x0B, 0x00, 0x02, 0x01, 0x00,
1145 // Extension: Signature algorithms
1146 // RSA/PKCS1/SHA256, RSA/PKCS1/SHA512
1147 0x00, 0x0D, 0x00, 0x06, 0x00, 0x04,
1148 0x04, 0x01, 0x06, 0x01,
1149 // Extension: Renegotiation Info => new connection
1150 0xFF, 0x01,
1151 0x00, 0x01, 0x00,
1152 // Extension: SCT (signed certificate timestamp)
1153 0x00, 0x12, 0x00,
1154 0x00,
1155 });
1156
1157 // Read server hello
1158 var ciphersuite: u16 = undefined;
1159 {
1160 const length = try handshake_record_length(reader);
1161 if (length < 44)
1162 return error.ServerMalformedResponse;
1163 {
1164 var hs_hdr_and_server_ver: [6]u8 = undefined;
1165 try hashing_reader.readNoEof(&hs_hdr_and_server_ver);
1166 if (hs_hdr_and_server_ver[0] != 0x02)
1167 return error.ServerMalformedResponse;
1168 if (!mem.eql(u8, hs_hdr_and_server_ver[4..6], "\x03\x03"))
1169 return error.ServerInvalidVersion;
1170 }
1171 try hashing_reader.readNoEof(&server_random);
1172
1173 // Just skip the session id for now
1174 const sess_id_len = try hashing_reader.readByte();
1175 if (sess_id_len != 0)
1176 try hashing_reader.skipBytes(sess_id_len, .{});
1177
1178 {
1179 ciphersuite = try hashing_reader.readIntBig(u16);
1180 var found = false;
1181 inline for (suites) |cs| {
1182 if (ciphersuite == cs.tag) {
1183 found = true;
1184 // TODO This segfaults stage1
1185 // break;
1186 }
1187 }
1188 if (!found)
1189 return error.ServerInvalidCipherSuite;
1190 }
1191
1192 // Compression method
1193 if ((try hashing_reader.readByte()) != 0x00)
1194 return error.ServerInvalidCompressionMethod;
1195
1196 const exts_length = try hashing_reader.readIntBig(u16);
1197 var ext_byte_idx: usize = 0;
1198 while (ext_byte_idx < exts_length) {
1199 var ext_tag: [2]u8 = undefined;
1200 try hashing_reader.readNoEof(&ext_tag);
1201
1202 const ext_len = try hashing_reader.readIntBig(u16);
1203 ext_byte_idx += 4 + ext_len;
1204 if (ext_tag[0] == 0xFF and ext_tag[1] == 0x01) {
1205 // Renegotiation info
1206 const renegotiation_info = try hashing_reader.readByte();
1207 if (ext_len != 0x01 or renegotiation_info != 0x00)
1208 return error.ServerInvalidRenegotiationData;
1209 } else if (ext_tag[0] == 0x00 and ext_tag[1] == 0x00) {
1210 // Server name
1211 if (ext_len != 0)
1212 try hashing_reader.skipBytes(ext_len, .{});
1213 } else if (ext_tag[0] == 0x00 and ext_tag[1] == 0x0B) {
1214 const format_count = try hashing_reader.readByte();
1215 var found_uncompressed = false;
1216 var i: usize = 0;
1217 while (i < format_count) : (i += 1) {
1218 const byte = try hashing_reader.readByte();
1219 if (byte == 0x0)
1220 found_uncompressed = true;
1221 }
1222 if (!found_uncompressed)
1223 return error.ServerInvalidECPointCompression;
1224 } else if (has_alpn and ext_tag[0] == 0x00 and ext_tag[1] == 0x10) {
1225 const alpn_ext_len = try hashing_reader.readIntBig(u16);
1226 if (alpn_ext_len != ext_len - 2)
1227 return error.ServerMalformedResponse;
1228 const str_len = try hashing_reader.readByte();
1229 var buf: [256]u8 = undefined;
1230 try hashing_reader.readNoEof(buf[0..str_len]);
1231 const found = for (options.protocols) |proto| {
1232 if (mem.eql(u8, proto, buf[0..str_len])) {
1233 protocol = proto;
1234 break true;
1235 }
1236 } else false;
1237 if (!found)
1238 return error.ServerInvalidProtocol;
1239 try hashing_reader.skipBytes(alpn_ext_len - str_len - 1, .{});
1240 } else return error.ServerInvalidExtension;
1241 }
1242 if (ext_byte_idx != exts_length)
1243 return error.ServerMalformedResponse;
1244 }
1245 // Read server certificates
1246 var certificate_public_key: x509.PublicKey = undefined;
1247 {
1248 const length = try handshake_record_length(reader);
1249 {
1250 var handshake_header: [4]u8 = undefined;
1251 try hashing_reader.readNoEof(&handshake_header);
1252 if (handshake_header[0] != 0x0b)
1253 return error.ServerMalformedResponse;
1254 }
1255 const certs_length = try hashing_reader.readIntBig(u24);
1256 const cert_verifier: CertificateVerifier = options.cert_verifier;
1257 switch (cert_verifier) {
1258 .none => certificate_public_key = try extract_cert_public_key(
1259 options.temp_allocator,
1260 hashing_reader,
1261 certs_length,
1262 ),
1263 .function => |f| {
1264 var reader_state = CertificateReaderState(@TypeOf(hashing_reader)){
1265 .reader = hashing_reader,
1266 .length = certs_length,
1267 };
1268 var cert_reader = CertificateReader(@TypeOf(hashing_reader)){ .context = &reader_state };
1269 certificate_public_key = try f(cert_reader);
1270 try hashing_reader.skipBytes(reader_state.length - reader_state.idx, .{});
1271 },
1272 .default => certificate_public_key = try default_cert_verifier(
1273 options.temp_allocator,
1274 hashing_reader,
1275 certs_length,
1276 options.trusted_certificates,
1277 hostname,
1278 ),
1279 }
1280 }
1281 errdefer certificate_public_key.deinit(options.temp_allocator);
1282 // Read server ephemeral public key
1283 var server_public_key_buf: [curves.max_pub_key_len(curvelist)]u8 = undefined;
1284 var curve_id: u16 = undefined;
1285 var curve_id_buf: [3]u8 = undefined;
1286 var pub_key_len: u8 = undefined;
1287 {
1288 const length = try handshake_record_length(reader);
1289 {
1290 var handshake_header: [4]u8 = undefined;
1291 try hashing_reader.readNoEof(&handshake_header);
1292 if (handshake_header[0] != 0x0c)
1293 return error.ServerMalformedResponse;
1294
1295 try hashing_reader.readNoEof(&curve_id_buf);
1296 if (curve_id_buf[0] != 0x03)
1297 return error.ServerMalformedResponse;
1298
1299 curve_id = mem.readIntBig(u16, curve_id_buf[1..]);
1300 var found = false;
1301 inline for (curvelist) |curve| {
1302 if (curve.tag == curve_id) {
1303 found = true;
1304 // @TODO This break segfaults stage1
1305 // break;
1306 }
1307 }
1308 if (!found)
1309 return error.ServerInvalidCurve;
1310 }
1311
1312 pub_key_len = try hashing_reader.readByte();
1313 inline for (curvelist) |curve| {
1314 if (curve.tag == curve_id) {
1315 if (curve.pub_key_len != pub_key_len)
1316 return error.ServerMalformedResponse;
1317 // @TODO This break segfaults stage1
1318 // break;
1319 }
1320 }
1321
1322 try hashing_reader.readNoEof(server_public_key_buf[0..pub_key_len]);
1323 if (curve_id != curves.x25519.tag) {
1324 if (server_public_key_buf[0] != 0x04)
1325 return error.ServerMalformedResponse;
1326 }
1327
1328 // Signed public key
1329 const signature_id = try hashing_reader.readIntBig(u16);
1330 const signature_len = try hashing_reader.readIntBig(u16);
1331
1332 var hash_buf: [64]u8 = undefined;
1333 var hash: []const u8 = undefined;
1334 const signature_algoritm: SignatureAlgorithm = switch (signature_id) {
1335 // RSA/PKCS1/SHA256
1336 0x0401 => block: {
1337 var sha256 = Sha256.init(.{});
1338 sha256.update(&client_random);
1339 sha256.update(&server_random);
1340 sha256.update(&curve_id_buf);
1341 // @TODO Should this always be \x20 instead?
1342 sha256.update(&[1]u8{pub_key_len});
1343 sha256.update(server_public_key_buf[0..pub_key_len]);
1344 sha256.final(hash_buf[0..32]);
1345 hash = hash_buf[0..32];
1346 break :block .rsa_sha256;
1347 },
1348 // RSA/PKCS1/SHA512
1349 0x0601 => block: {
1350 var sha512 = Sha512.init(.{});
1351 sha512.update(&client_random);
1352 sha512.update(&server_random);
1353 sha512.update(&curve_id_buf);
1354 sha512.update(&[1]u8{pub_key_len});
1355 sha512.update(server_public_key_buf[0..pub_key_len]);
1356 sha512.final(hash_buf[0..64]);
1357 hash = hash_buf[0..64];
1358 break :block .rsa_sha512;
1359 },
1360 else => return error.ServerInvalidSignatureAlgorithm,
1361 };
1362 const signature_bytes = try options.temp_allocator.alloc(u8, signature_len);
1363 defer options.temp_allocator.free(signature_bytes);
1364 try hashing_reader.readNoEof(signature_bytes);
1365
1366 if (!try verify_signature(
1367 options.temp_allocator,
1368 signature_algoritm,
1369 .{ .data = signature_bytes, .bit_len = signature_len * 8 },
1370 hash,
1371 certificate_public_key,
1372 ))
1373 return error.ServerInvalidSignature;
1374
1375 certificate_public_key.deinit(options.temp_allocator);
1376 certificate_public_key = x509.PublicKey.empty;
1377 }
1378 // Read server hello done
1379 {
1380 const length = try handshake_record_length(reader);
1381 const is_bytes = try hashing_reader.isBytes("\x0e\x00\x00\x00");
1382 if (length != 4 or !is_bytes)
1383 return error.ServerMalformedResponse;
1384 }
1385
1386 // Generate keys for the session
1387 const client_key_pair = curves.make_key_pair(curvelist, curve_id, rand);
1388
1389 // Client key exchange
1390 try writer.writeAll(&[3]u8{ 0x16, 0x03, 0x03 });
1391 try writer.writeIntBig(u16, pub_key_len + 5);
1392 try hashing_writer.writeAll(&[5]u8{ 0x10, 0x00, 0x00, pub_key_len + 1, pub_key_len });
1393
1394 inline for (curvelist) |curve| {
1395 if (curve.tag == curve_id) {
1396 const actual_len = @typeInfo(std.meta.fieldInfo(curve.Keys, .public_key).field_type).Array.len;
1397 if (pub_key_len == actual_len + 1) {
1398 try hashing_writer.writeByte(0x04);
1399 } else {
1400 std.debug.assert(pub_key_len == actual_len);
1401 }
1402 try hashing_writer.writeAll(&@field(client_key_pair, curve.name).public_key);
1403 break;
1404 }
1405 }
1406
1407 // Client encryption keys calculation for ECDHE_RSA cipher suites with SHA256 hash
1408 var master_secret: [48]u8 = undefined;
1409 var key_data: ciphers.KeyData(suites) = undefined;
1410 {
1411 var pre_master_secret_buf: [curves.max_pre_master_secret_len(curvelist)]u8 = undefined;
1412 const pre_master_secret = try curves.make_pre_master_secret(
1413 curvelist,
1414 curve_id,
1415 client_key_pair,
1416 &pre_master_secret_buf,
1417 server_public_key_buf,
1418 );
1419
1420 var seed: [77]u8 = undefined;
1421 seed[0..13].* = "master secret".*;
1422 seed[13..45].* = client_random;
1423 seed[45..77].* = server_random;
1424
1425 var a1: [32 + seed.len]u8 = undefined;
1426 Hmac256.create(a1[0..32], &seed, pre_master_secret);
1427 var a2: [32 + seed.len]u8 = undefined;
1428 Hmac256.create(a2[0..32], a1[0..32], pre_master_secret);
1429
1430 a1[32..].* = seed;
1431 a2[32..].* = seed;
1432
1433 var p1: [32]u8 = undefined;
1434 Hmac256.create(&p1, &a1, pre_master_secret);
1435 var p2: [32]u8 = undefined;
1436 Hmac256.create(&p2, &a2, pre_master_secret);
1437
1438 master_secret[0..32].* = p1;
1439 master_secret[32..48].* = p2[0..16].*;
1440
1441 // Key expansion
1442 seed[0..13].* = "key expansion".*;
1443 seed[13..45].* = server_random;
1444 seed[45..77].* = client_random;
1445 a1[32..].* = seed;
1446 a2[32..].* = seed;
1447
1448 const KeyExpansionState = struct {
1449 seed: *const [77]u8,
1450 a1: *[32 + seed.len]u8,
1451 a2: *[32 + seed.len]u8,
1452 master_secret: *const [48]u8,
1453 };
1454
1455 const next_32_bytes = struct {
1456 inline fn f(
1457 state: *KeyExpansionState,
1458 comptime chunk_idx: comptime_int,
1459 chunk: *[32]u8,
1460 ) void {
1461 if (chunk_idx == 0) {
1462 Hmac256.create(state.a1[0..32], state.seed, state.master_secret);
1463 Hmac256.create(chunk, state.a1, state.master_secret);
1464 } else if (chunk_idx % 2 == 1) {
1465 Hmac256.create(state.a2[0..32], state.a1[0..32], state.master_secret);
1466 Hmac256.create(chunk, state.a2, state.master_secret);
1467 } else {
1468 Hmac256.create(state.a1[0..32], state.a2[0..32], state.master_secret);
1469 Hmac256.create(chunk, state.a1, state.master_secret);
1470 }
1471 }
1472 }.f;
1473 var state = KeyExpansionState{
1474 .seed = &seed,
1475 .a1 = &a1,
1476 .a2 = &a2,
1477 .master_secret = &master_secret,
1478 };
1479
1480 key_data = ciphers.key_expansion(suites, ciphersuite, &state, next_32_bytes);
1481 }
1482
1483 // Client change cipher spec and client handshake finished
1484 {
1485 try writer.writeAll(&[6]u8{
1486 // Client change cipher spec
1487 0x14, 0x03, 0x03,
1488 0x00, 0x01, 0x01,
1489 });
1490 // The message we need to encrypt is the following:
1491 // 0x14 0x00 0x00 0x0c
1492 // <12 bytes of verify_data>
1493 // seed = "client finished" + SHA256(all handshake messages)
1494 // a1 = HMAC-SHA256(key=MasterSecret, data=seed)
1495 // p1 = HMAC-SHA256(key=MasterSecret, data=a1 + seed)
1496 // verify_data = p1[0..12]
1497 var verify_message: [16]u8 = undefined;
1498 verify_message[0..4].* = "\x14\x00\x00\x0C".*;
1499 {
1500 var seed: [47]u8 = undefined;
1501 seed[0..15].* = "client finished".*;
1502 // We still need to update the hash one time, so we copy
1503 // to get the current digest here.
1504 var hash_copy = handshake_record_hash;
1505 hash_copy.final(seed[15..47]);
1506
1507 var a1: [32 + seed.len]u8 = undefined;
1508 Hmac256.create(a1[0..32], &seed, &master_secret);
1509 a1[32..].* = seed;
1510 var p1: [32]u8 = undefined;
1511 Hmac256.create(&p1, &a1, &master_secret);
1512 verify_message[4..16].* = p1[0..12].*;
1513 }
1514 handshake_record_hash.update(&verify_message);
1515
1516 inline for (suites) |cs| {
1517 if (cs.tag == ciphersuite) {
1518 try cs.raw_write(
1519 256,
1520 rand,
1521 &key_data,
1522 writer,
1523 [3]u8{ 0x16, 0x03, 0x03 },
1524 0,
1525 &verify_message,
1526 );
1527 }
1528 }
1529 }
1530
1531 // Server change cipher spec
1532 {
1533 const length = try record_length(0x14, reader);
1534 const next_byte = try reader.readByte();
1535 if (length != 1 or next_byte != 0x01)
1536 return error.ServerMalformedResponse;
1537 }
1538 // Server handshake finished
1539 {
1540 const length = try handshake_record_length(reader);
1541
1542 var verify_message: [16]u8 = undefined;
1543 verify_message[0..4].* = "\x14\x00\x00\x0C".*;
1544 {
1545 var seed: [47]u8 = undefined;
1546 seed[0..15].* = "server finished".*;
1547 handshake_record_hash.final(seed[15..47]);
1548 var a1: [32 + seed.len]u8 = undefined;
1549 Hmac256.create(a1[0..32], &seed, &master_secret);
1550 a1[32..].* = seed;
1551 var p1: [32]u8 = undefined;
1552 Hmac256.create(&p1, &a1, &master_secret);
1553 verify_message[4..16].* = p1[0..12].*;
1554 }
1555
1556 inline for (suites) |cs| {
1557 if (cs.tag == ciphersuite) {
1558 if (!try cs.check_verify_message(&key_data, length, reader, verify_message))
1559 return error.ServerInvalidVerifyData;
1560 }
1561 }
1562 }
1563
1564 return Client(@TypeOf(reader), @TypeOf(writer), suites, has_alpn){
1565 .ciphersuite = ciphersuite,
1566 .key_data = key_data,
1567 .state = ciphers.client_state_default(suites, ciphersuite),
1568 .rand = rand,
1569 .parent_reader = reader,
1570 .parent_writer = writer,
1571 .protocol = protocol,
1572 };
1573}
1574
1575pub fn Client(
1576 comptime _Reader: type,
1577 comptime _Writer: type,
1578 comptime _ciphersuites: anytype,
1579 comptime has_protocol: bool,
1580) type {
1581 return struct {
1582 const ReaderError = _Reader.Error || ServerAlert || error{ ServerMalformedResponse, ServerInvalidVersion };
1583 pub const Reader = std.io.Reader(*@This(), ReaderError, read);
1584 pub const Writer = std.io.Writer(*@This(), _Writer.Error, write);
1585
1586 ciphersuite: u16,
1587 client_seq: u64 = 1,
1588 server_seq: u64 = 1,
1589 key_data: ciphers.KeyData(_ciphersuites),
1590 state: ciphers.ClientState(_ciphersuites),
1591 rand: *std.rand.Random,
1592
1593 parent_reader: _Reader,
1594 parent_writer: _Writer,
1595
1596 protocol: if (has_protocol) []const u8 else void,
1597
1598 pub fn reader(self: *@This()) Reader {
1599 return .{ .context = self };
1600 }
1601
1602 pub fn writer(self: *@This()) Writer {
1603 return .{ .context = self };
1604 }
1605
1606 pub fn read(self: *@This(), buffer: []u8) ReaderError!usize {
1607 inline for (_ciphersuites) |cs| {
1608 if (self.ciphersuite == cs.tag) {
1609 // @TODO Make this buffer size configurable
1610 return try cs.read(
1611 1024,
1612 &@field(self.state, cs.name),
1613 &self.key_data,
1614 self.parent_reader,
1615 &self.server_seq,
1616 buffer,
1617 );
1618 }
1619 }
1620 unreachable;
1621 }
1622
1623 pub fn write(self: *@This(), buffer: []const u8) _Writer.Error!usize {
1624 if (buffer.len == 0) return 0;
1625
1626 inline for (_ciphersuites) |cs| {
1627 if (self.ciphersuite == cs.tag) {
1628 // @TODO Make this buffer size configurable
1629 const curr_bytes = @truncate(u16, std.math.min(buffer.len, 1024));
1630 try cs.raw_write(
1631 1024,
1632 self.rand,
1633 &self.key_data,
1634 self.parent_writer,
1635 [3]u8{ 0x17, 0x03, 0x03 },
1636 self.client_seq,
1637 buffer[0..curr_bytes],
1638 );
1639 self.client_seq += 1;
1640 return curr_bytes;
1641 }
1642 }
1643 unreachable;
1644 }
1645
1646 pub fn close_notify(self: *@This()) !void {
1647 inline for (_ciphersuites) |cs| {
1648 if (self.ciphersuite == cs.tag) {
1649 try cs.raw_write(
1650 1024,
1651 self.rand,
1652 &self.key_data,
1653 self.parent_writer,
1654 [3]u8{ 0x15, 0x03, 0x03 },
1655 self.client_seq,
1656 "\x01\x00",
1657 );
1658 self.client_seq += 1;
1659 return;
1660 }
1661 }
1662 unreachable;
1663 }
1664 };
1665}
1666
1667test "HTTPS request on wikipedia main page" {
1668 const sock = try std.net.tcpConnectToHost(std.testing.allocator, "en.wikipedia.org", 443);
1669 defer sock.close();
1670
1671 var fbs = std.io.fixedBufferStream(@embedFile("../test/DigiCertHighAssuranceEVRootCA.crt.pem"));
1672 var trusted_chain = try x509.TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader());
1673 defer trusted_chain.deinit();
1674
1675 // @TODO Remove this once std.crypto.rand works in .evented mode
1676 var rand = blk: {
1677 var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
1678 try std.os.getrandom(&seed);
1679 break :blk &std.rand.DefaultCsprng.init(seed).random;
1680 };
1681
1682 var client = try client_connect(.{
1683 .rand = rand,
1684 .reader = sock.reader(),
1685 .writer = sock.writer(),
1686 .cert_verifier = .default,
1687 .temp_allocator = std.testing.allocator,
1688 .trusted_certificates = trusted_chain.data.items,
1689 .ciphersuites = .{ciphersuites.ECDHE_RSA_Chacha20_Poly1305},
1690 .protocols = &[_][]const u8{"http/1.1"},
1691 .curves = .{curves.x25519},
1692 }, "en.wikipedia.org");
1693 defer client.close_notify() catch {};
1694
1695 std.testing.expectEqualStrings("http/1.1", client.protocol);
1696 try client.writer().writeAll("GET /wiki/Main_Page HTTP/1.1\r\nHost: en.wikipedia.org\r\nAccept: */*\r\n\r\n");
1697
1698 {
1699 const header = try client.reader().readUntilDelimiterAlloc(std.testing.allocator, '\n', std.math.maxInt(usize));
1700 std.testing.expectEqualStrings("HTTP/1.1 200 OK", mem.trim(u8, header, &std.ascii.spaces));
1701 std.testing.allocator.free(header);
1702 }
1703
1704 // Skip the rest of the headers expect for Content-Length
1705 var content_length: ?usize = null;
1706 hdr_loop: while (true) {
1707 const header = try client.reader().readUntilDelimiterAlloc(std.testing.allocator, '\n', std.math.maxInt(usize));
1708 defer std.testing.allocator.free(header);
1709
1710 const hdr_contents = mem.trim(u8, header, &std.ascii.spaces);
1711 if (hdr_contents.len == 0) {
1712 break :hdr_loop;
1713 }
1714
1715 if (mem.startsWith(u8, hdr_contents, "Content-Length: ")) {
1716 content_length = try std.fmt.parseUnsigned(usize, hdr_contents[16..], 10);
1717 }
1718 }
1719 std.testing.expect(content_length != null);
1720 const html_contents = try std.testing.allocator.alloc(u8, content_length.?);
1721 defer std.testing.allocator.free(html_contents);
1722
1723 try client.reader().readNoEof(html_contents);
1724}
1725
1726test "HTTPS request on twitch oath2 endpoint" {
1727 const sock = try std.net.tcpConnectToHost(std.testing.allocator, "id.twitch.tv", 443);
1728 defer sock.close();
1729
1730 // @TODO Remove this once std.crypto.rand works in .evented mode
1731 var rand = blk: {
1732 var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
1733 try std.os.getrandom(&seed);
1734 break :blk &std.rand.DefaultCsprng.init(seed).random;
1735 };
1736
1737 var client = try client_connect(.{
1738 .rand = rand,
1739 .temp_allocator = std.testing.allocator,
1740 .reader = sock.reader(),
1741 .writer = sock.writer(),
1742 .cert_verifier = .none,
1743 .protocols = &[_][]const u8{"http/1.1"},
1744 }, "id.twitch.tv");
1745 std.testing.expectEqualStrings("http/1.1", client.protocol);
1746 defer client.close_notify() catch {};
1747
1748 try client.writer().writeAll("GET /oauth2/validate HTTP/1.1\r\nHost: id.twitch.tv\r\nAccept: */*\r\n\r\n");
1749 var content_length: ?usize = null;
1750 hdr_loop: while (true) {
1751 const header = try client.reader().readUntilDelimiterAlloc(std.testing.allocator, '\n', std.math.maxInt(usize));
1752 defer std.testing.allocator.free(header);
1753
1754 const hdr_contents = mem.trim(u8, header, &std.ascii.spaces);
1755 if (hdr_contents.len == 0) {
1756 break :hdr_loop;
1757 }
1758
1759 if (mem.startsWith(u8, hdr_contents, "Content-Length: ")) {
1760 content_length = try std.fmt.parseUnsigned(usize, hdr_contents[16..], 10);
1761 }
1762 }
1763 std.testing.expect(content_length != null);
1764 const html_contents = try std.testing.allocator.alloc(u8, content_length.?);
1765 defer std.testing.allocator.free(html_contents);
1766
1767 try client.reader().readNoEof(html_contents);
1768}
1769
1770test "Connecting to expired.badssl.com returns an error" {
1771 const sock = try std.net.tcpConnectToHost(std.testing.allocator, "expired.badssl.com", 443);
1772 defer sock.close();
1773
1774 var fbs = std.io.fixedBufferStream(@embedFile("../test/DigiCertGlobalRootCA.crt.pem"));
1775 var trusted_chain = try x509.TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader());
1776 defer trusted_chain.deinit();
1777
1778 // @TODO Remove this once std.crypto.rand works in .evented mode
1779 var rand = blk: {
1780 var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
1781 try std.os.getrandom(&seed);
1782 break :blk &std.rand.DefaultCsprng.init(seed).random;
1783 };
1784
1785 std.testing.expectError(error.CertificateVerificationFailed, client_connect(.{
1786 .rand = rand,
1787 .reader = sock.reader(),
1788 .writer = sock.writer(),
1789 .cert_verifier = .default,
1790 .temp_allocator = std.testing.allocator,
1791 .trusted_certificates = trusted_chain.data.items,
1792 }, "expired.badssl.com"));
1793}
1794
1795test "Connecting to wrong.host.badssl.com returns an error" {
1796 const sock = try std.net.tcpConnectToHost(std.testing.allocator, "wrong.host.badssl.com", 443);
1797 defer sock.close();
1798
1799 var fbs = std.io.fixedBufferStream(@embedFile("../test/DigiCertGlobalRootCA.crt.pem"));
1800 var trusted_chain = try x509.TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader());
1801 defer trusted_chain.deinit();
1802
1803 // @TODO Remove this once std.crypto.rand works in .evented mode
1804 var rand = blk: {
1805 var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
1806 try std.os.getrandom(&seed);
1807 break :blk &std.rand.DefaultCsprng.init(seed).random;
1808 };
1809
1810 std.testing.expectError(error.CertificateVerificationFailed, client_connect(.{
1811 .rand = rand,
1812 .reader = sock.reader(),
1813 .writer = sock.writer(),
1814 .cert_verifier = .default,
1815 .temp_allocator = std.testing.allocator,
1816 .trusted_certificates = trusted_chain.data.items,
1817 }, "wrong.host.badssl.com"));
1818}
1819
1820test "Connecting to self-signed.badssl.com returns an error" {
1821 const sock = try std.net.tcpConnectToHost(std.testing.allocator, "self-signed.badssl.com", 443);
1822 defer sock.close();
1823
1824 var fbs = std.io.fixedBufferStream(@embedFile("../test/DigiCertGlobalRootCA.crt.pem"));
1825 var trusted_chain = try x509.TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader());
1826 defer trusted_chain.deinit();
1827
1828 // @TODO Remove this once std.crypto.rand works in .evented mode
1829 var rand = blk: {
1830 var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
1831 try std.os.getrandom(&seed);
1832 break :blk &std.rand.DefaultCsprng.init(seed).random;
1833 };
1834
1835 std.testing.expectError(error.CertificateVerificationFailed, client_connect(.{
1836 .rand = rand,
1837 .reader = sock.reader(),
1838 .writer = sock.writer(),
1839 .cert_verifier = .default,
1840 .temp_allocator = std.testing.allocator,
1841 .trusted_certificates = trusted_chain.data.items,
1842 }, "self-signed.badssl.com"));
1843}
libs/iguanatls/src/x509.zig deleted-721
......@@ -1,721 +0,0 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const mem = std.mem;
4const trait = std.meta.trait;
5
6const asn1 = @import("asn1.zig");
7
8// zig fmt: off
9// http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8
10// @TODO add backing integer, values
11pub const CurveId = enum {
12 sect163k1, sect163r1, sect163r2, sect193r1,
13 sect193r2, sect233k1, sect233r1, sect239k1,
14 sect283k1, sect283r1, sect409k1, sect409r1,
15 sect571k1, sect571r1, secp160k1, secp160r1,
16 secp160r2, secp192k1, secp192r1, secp224k1,
17 secp224r1, secp256k1, secp256r1, secp384r1,
18 secp521r1,brainpoolP256r1, brainpoolP384r1,
19 brainpoolP512r1, curve25519, curve448,
20};
21// zig fmt: on
22
23pub const PublicKey = union(enum) {
24 pub const empty = PublicKey{ .ec = .{ .id = undefined, .curve_point = &[0]u8{} } };
25
26 /// RSA public key
27 rsa: struct {
28 //Positive std.math.big.int.Const numbers.
29 modulus: []const usize,
30 exponent: []const usize,
31 },
32 /// Elliptic curve public key
33 ec: struct {
34 id: CurveId,
35 /// Public curve point (uncompressed format)
36 curve_point: []const u8,
37 },
38
39 pub fn deinit(self: @This(), alloc: *Allocator) void {
40 switch (self) {
41 .rsa => |rsa| {
42 alloc.free(rsa.modulus);
43 alloc.free(rsa.exponent);
44 },
45 .ec => |ec| alloc.free(ec.curve_point),
46 }
47 }
48
49 pub fn eql(self: @This(), other: @This()) bool {
50 if (@as(std.meta.Tag(@This()), self) != @as(std.meta.Tag(@This()), other))
51 return false;
52 switch (self) {
53 .rsa => |mod_exp| return mem.eql(usize, mod_exp.exponent, other.rsa.exponent) and
54 mem.eql(usize, mod_exp.modulus, other.rsa.modulus),
55 .ec => |ec| return ec.id == other.ec.id and mem.eql(u8, ec.curve_point, other.ec.curve_point),
56 }
57 }
58};
59
60pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey {
61 if ((try reader.readByte()) != 0x30)
62 return error.MalformedDER;
63 const seq_len = try asn1.der.parse_length(reader);
64
65 if ((try reader.readByte()) != 0x06)
66 return error.MalformedDER;
67 const oid_bytes = try asn1.der.parse_length(reader);
68 if (oid_bytes == 9) {
69 // @TODO This fails in async if merged with the if
70 if (!try reader.isBytes(&[9]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1 }))
71 return error.MalformedDER;
72 // OID is 1.2.840.113549.1.1.1
73 // RSA key
74 // Skip past the NULL
75 const null_byte = try reader.readByte();
76 if (null_byte != 0x05)
77 return error.MalformedDER;
78 const null_len = try asn1.der.parse_length(reader);
79 if (null_len != 0x00)
80 return error.MalformedDER;
81 {
82 // BitString next!
83 if ((try reader.readByte()) != 0x03)
84 return error.MalformedDER;
85 _ = try asn1.der.parse_length(reader);
86 const bit_string_unused_bits = try reader.readByte();
87 if (bit_string_unused_bits != 0)
88 return error.MalformedDER;
89
90 if ((try reader.readByte()) != 0x30)
91 return error.MalformedDER;
92 _ = try asn1.der.parse_length(reader);
93
94 // Modulus
95 if ((try reader.readByte()) != 0x02)
96 return error.MalformedDER;
97 const modulus = try asn1.der.parse_int(allocator, reader);
98 errdefer allocator.free(modulus.limbs);
99 if (!modulus.positive) return error.MalformedDER;
100 // Exponent
101 if ((try reader.readByte()) != 0x02)
102 return error.MalformedDER;
103 const exponent = try asn1.der.parse_int(allocator, reader);
104 errdefer allocator.free(exponent.limbs);
105 if (!exponent.positive) return error.MalformedDER;
106 return PublicKey{
107 .rsa = .{
108 .modulus = modulus.limbs,
109 .exponent = exponent.limbs,
110 },
111 };
112 }
113 } else if (oid_bytes == 7) {
114 // @TODO This fails in async if merged with the if
115 if (!try reader.isBytes(&[7]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }))
116 return error.MalformedDER;
117 // OID is 1.2.840.10045.2.1
118 // Elliptical curve
119 // We only support named curves, for which the parameter field is an OID.
120 const oid_tag = try reader.readByte();
121 if (oid_tag != 0x06)
122 return error.MalformedDER;
123 const curve_oid_bytes = try asn1.der.parse_length(reader);
124
125 var key: PublicKey = undefined;
126 if (curve_oid_bytes == 5) {
127 if (!try reader.isBytes(&[4]u8{ 0x2B, 0x81, 0x04, 0x00 }))
128 return error.MalformedDER;
129 // 1.3.132.0.{34, 35}
130 const last_byte = try reader.readByte();
131 if (last_byte == 0x22)
132 key = .{ .ec = .{ .id = .secp384r1, .curve_point = undefined } }
133 else if (last_byte == 0x23)
134 key = .{ .ec = .{ .id = .secp521r1, .curve_point = undefined } }
135 else
136 return error.MalformedDER;
137 } else if (curve_oid_bytes == 8) {
138 if (!try reader.isBytes(&[8]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x3, 0x1, 0x7 }))
139 return error.MalformedDER;
140 key = .{ .ec = .{ .id = .secp256r1, .curve_point = undefined } };
141 } else {
142 return error.MalformedDER;
143 }
144
145 if ((try reader.readByte()) != 0x03)
146 return error.MalformedDER;
147 const byte_len = try asn1.der.parse_length(reader);
148 const unused_bits = try reader.readByte();
149 const bit_count = (byte_len - 1) * 8 - unused_bits;
150 if (bit_count % 8 != 0)
151 return error.MalformedDER;
152 const bit_memory = try allocator.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable);
153 errdefer allocator.free(bit_memory);
154 try reader.readNoEof(bit_memory[0 .. byte_len - 1]);
155
156 key.ec.curve_point = bit_memory;
157 return key;
158 }
159 return error.MalformedDER;
160}
161
162pub fn DecodeDERError(comptime Reader: type) type {
163 return Reader.Error || error{
164 MalformedPEM,
165 MalformedDER,
166 EndOfStream,
167 OutOfMemory,
168 };
169}
170
171pub const TrustAnchor = struct {
172 /// Subject distinguished name
173 dn: []const u8,
174 /// A "CA" anchor is deemed fit to verify signatures on certificates.
175 /// A "non-CA" anchor is accepted only for direct trust (server's certificate
176 /// name and key match the anchor).
177 is_ca: bool = false,
178 public_key: PublicKey,
179
180 const CaptureState = struct {
181 self: *TrustAnchor,
182 allocator: *Allocator,
183 dn_allocated: bool = false,
184 pk_allocated: bool = false,
185 };
186 fn initSubjectDn(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
187 const dn_mem = try state.allocator.alloc(u8, length);
188 errdefer state.allocator.free(dn_mem);
189 try reader.readNoEof(dn_mem);
190 state.self.dn = dn_mem;
191 state.dn_allocated = true;
192 }
193
194 fn processExtension(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
195 const object_id = try asn1.der.parse_value(state.allocator, reader);
196 defer object_id.deinit(state.allocator);
197 if (object_id != .object_identifier) return error.DoesNotMatchSchema;
198 if (object_id.object_identifier.len != 4)
199 return;
200
201 const data = object_id.object_identifier.data;
202 // Basic constraints extension
203 if (data[0] != 2 or data[1] != 5 or data[2] != 29 or data[3] != 19)
204 return;
205
206 const basic_constraints = try asn1.der.parse_value(state.allocator, reader);
207 defer basic_constraints.deinit(state.allocator);
208
209 switch (basic_constraints) {
210 .bool => |b| state.self.is_ca = true,
211 .octet_string => |s| {
212 if (s.len != 5 or s[0] != 0x30 or s[1] != 0x03 or s[2] != 0x01 or s[3] != 0x01)
213 return error.DoesNotMatchSchema;
214 state.self.is_ca = s[4] != 0x00;
215 },
216 else => return error.DoesNotMatchSchema,
217 }
218 }
219
220 fn initExtensions(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
221 const schema = .{
222 .sequence_of,
223 .{ .capture, 0, .sequence },
224 };
225 const captures = .{
226 state, processExtension,
227 };
228 try asn1.der.parse_schema(schema, captures, reader);
229 }
230
231 fn initPublicKeyInfo(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
232 state.self.public_key = try parse_public_key(state.allocator, reader);
233 state.pk_allocated = true;
234 }
235
236 /// Initialize a trusted anchor from distinguished encoding rules (DER) encoded data
237 pub fn create(allocator: *Allocator, der_reader: anytype) DecodeDERError(@TypeOf(der_reader))!@This() {
238 var self: @This() = undefined;
239 self.is_ca = false;
240 // https://tools.ietf.org/html/rfc5280#page-117
241 const schema = .{
242 .sequence, .{
243 // tbsCertificate
244 .{
245 .sequence,
246 .{
247 .{ .context_specific, 0 }, // version
248 .{.int}, // serialNumber
249 .{.sequence}, // signature
250 .{.sequence}, // issuer
251 .{.sequence}, // validity,
252 .{ .capture, 0, .sequence }, // subject
253 .{ .capture, 1, .sequence }, // subjectPublicKeyInfo
254 .{ .optional, .context_specific, 1 }, // issuerUniqueID
255 .{ .optional, .context_specific, 2 }, // subjectUniqueID
256 .{ .capture, 2, .optional, .context_specific, 3 }, // extensions
257 },
258 },
259 // signatureAlgorithm
260 .{.sequence},
261 // signatureValue
262 .{.bit_string},
263 },
264 };
265
266 var capture_state = CaptureState{
267 .self = &self,
268 .allocator = allocator,
269 };
270 const captures = .{
271 &capture_state, initSubjectDn,
272 &capture_state, initPublicKeyInfo,
273 &capture_state, initExtensions,
274 };
275
276 errdefer {
277 if (capture_state.dn_allocated)
278 allocator.free(self.dn);
279 if (capture_state.pk_allocated)
280 self.public_key.deinit(allocator);
281 }
282
283 asn1.der.parse_schema(schema, captures, der_reader) catch |err| switch (err) {
284 error.InvalidLength,
285 error.InvalidTag,
286 error.InvalidContainerLength,
287 error.DoesNotMatchSchema,
288 => return error.MalformedDER,
289 else => |e| return e,
290 };
291 return self;
292 }
293
294 pub fn deinit(self: @This(), alloc: *Allocator) void {
295 alloc.free(self.dn);
296 self.public_key.deinit(alloc);
297 }
298
299 pub fn format(self: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
300 try writer.print(
301 \\CERTIFICATE
302 \\-----------
303 \\IS CA: {}
304 \\Subject distinguished name (encoded):
305 \\{X}
306 \\Public key:
307 \\
308 , .{ self.is_ca, self.dn });
309
310 switch (self.public_key) {
311 .rsa => |mod_exp| {
312 const modulus = std.math.big.int.Const{ .positive = true, .limbs = mod_exp.modulus };
313 const exponent = std.math.big.int.Const{ .positive = true, .limbs = mod_exp.exponent };
314 try writer.print(
315 \\RSA
316 \\modulus: {}
317 \\exponent: {}
318 \\
319 , .{
320 modulus,
321 exponent,
322 });
323 },
324 .ec => |ec| {
325 try writer.print(
326 \\EC (Curve: {})
327 \\point: {}
328 \\
329 , .{
330 ec.id,
331 ec.curve_point,
332 });
333 },
334 }
335
336 try writer.writeAll(
337 \\-----------
338 \\
339 );
340 }
341};
342
343pub const TrustAnchorChain = struct {
344 data: std.ArrayList(TrustAnchor),
345
346 pub fn from_pem(allocator: *Allocator, pem_reader: anytype) DecodeDERError(@TypeOf(pem_reader))!@This() {
347 var self = @This(){ .data = std.ArrayList(TrustAnchor).init(allocator) };
348 errdefer self.deinit();
349
350 var it = pemCertificateIterator(pem_reader);
351 while (try it.next()) |cert_reader| {
352 var buffered = std.io.bufferedReader(cert_reader);
353 const anchor = try TrustAnchor.create(allocator, buffered.reader());
354 errdefer anchor.deinit(allocator);
355 try self.data.append(anchor);
356 }
357 return self;
358 }
359
360 pub fn deinit(self: @This()) void {
361 const alloc = self.data.allocator;
362 for (self.data.items) |ta| ta.deinit(alloc);
363 self.data.deinit();
364 }
365};
366
367fn PEMSectionReader(comptime Reader: type) type {
368 const Error = Reader.Error || error{MalformedPEM};
369 const read = struct {
370 fn f(it: *PEMCertificateIterator(Reader), buf: []u8) Error!usize {
371 var out_idx: usize = 0;
372 if (it.waiting_chars_len > 0) {
373 const rest_written = std.math.min(it.waiting_chars_len, buf.len);
374 while (out_idx < rest_written) : (out_idx += 1) {
375 buf[out_idx] = it.waiting_chars[out_idx];
376 }
377
378 it.waiting_chars_len -= rest_written;
379 if (it.waiting_chars_len != 0) {
380 std.mem.copy(u8, it.waiting_chars[0..], it.waiting_chars[rest_written..]);
381 }
382
383 if (out_idx == buf.len) {
384 return out_idx;
385 }
386 }
387 if (it.state != .in_section)
388 return out_idx;
389
390 var base64_buf: [4]u8 = undefined;
391 var base64_idx: usize = 0;
392 while (true) {
393 const byte = it.reader.readByte() catch |err| switch (err) {
394 error.EndOfStream => return out_idx,
395 else => |e| return e,
396 };
397
398 if (byte == '-') {
399 if (it.reader.isBytes("----END ") catch |err| switch (err) {
400 error.EndOfStream => return error.MalformedPEM,
401 else => |e| return e,
402 }) {
403 try it.reader.skipUntilDelimiterOrEof('\n');
404 it.state = .none;
405 return out_idx;
406 } else return error.MalformedPEM;
407 } else if (byte == '\r') {
408 if ((it.reader.readByte() catch |err| switch (err) {
409 error.EndOfStream => return error.MalformedPEM,
410 else => |e| return e,
411 }) != '\n')
412 return error.MalformedPEM;
413 continue;
414 } else if (byte == '\n')
415 continue;
416
417 base64_buf[base64_idx] = byte;
418 base64_idx += 1;
419 if (base64_idx == base64_buf.len) {
420 base64_idx = 0;
421
422 const out_len = std.base64.standard_decoder.calcSize(&base64_buf) catch
423 return error.MalformedPEM;
424
425 const rest_chars = if (out_len > buf.len - out_idx)
426 out_len - (buf.len - out_idx)
427 else
428 0;
429 const buf_chars = out_len - rest_chars;
430
431 var res_buffer: [3]u8 = undefined;
432 std.base64.standard_decoder_unsafe.decode(res_buffer[0..out_len], &base64_buf);
433
434 var i: u3 = 0;
435 while (i < buf_chars) : (i += 1) {
436 buf[out_idx] = res_buffer[i];
437 out_idx += 1;
438 }
439
440 if (rest_chars > 0) {
441 mem.copy(u8, &it.waiting_chars, res_buffer[i..]);
442 it.waiting_chars_len = @intCast(u2, rest_chars);
443 }
444 if (out_idx == buf.len)
445 return out_idx;
446 }
447 }
448 }
449 }.f;
450
451 return std.io.Reader(
452 *PEMCertificateIterator(Reader),
453 Error,
454 read,
455 );
456}
457
458fn PEMCertificateIterator(comptime Reader: type) type {
459 return struct {
460 pub const SectionReader = PEMSectionReader(Reader);
461 pub const NextError = SectionReader.Error || error{EndOfStream};
462
463 reader: Reader,
464 // Internal state for the iterator and the current reader.
465 state: enum {
466 none,
467 in_other,
468 in_section,
469 } = .none,
470 waiting_chars: [4]u8 = undefined,
471 waiting_chars_len: u2 = 0,
472
473 // @TODO More verification, this will accept lots of invalid PEM
474 pub fn next(self: *@This()) NextError!?SectionReader {
475 self.waiting_chars_len = 0;
476 while (true) {
477 const byte = self.reader.readByte() catch |err| switch (err) {
478 error.EndOfStream => if (self.state == .none)
479 return null
480 else
481 return error.EndOfStream,
482 else => |e| return e,
483 };
484
485 switch (self.state) {
486 .none => switch (byte) {
487 '#' => {
488 try self.reader.skipUntilDelimiterOrEof('\n');
489 continue;
490 },
491 '\r', '\n', ' ', '\t' => continue,
492 '-' => {
493 if (try self.reader.isBytes("----BEGIN ")) {
494 const first_section_byte = try self.reader.readByte();
495 if (first_section_byte == 'C') {
496 const rest = "ERTIFICATE";
497 var matched: usize = 0;
498 while ((try self.reader.readByte()) == rest[matched]) : (matched += 1) {
499 if (matched == rest.len - 1) {
500 try self.reader.skipUntilDelimiterOrEof('\n');
501 self.state = .in_section;
502 return SectionReader{ .context = self };
503 }
504 }
505 try self.reader.skipUntilDelimiterOrEof('\n');
506 self.state = .in_other;
507 continue;
508 } else if (first_section_byte == 'X') {
509 const rest = ".509 CERTIFICATE";
510 var matched: usize = 0;
511 while ((try self.reader.readByte()) == rest[matched]) : (matched += 1) {
512 if (matched == rest.len - 1) {
513 try self.reader.skipUntilDelimiterOrEof('\n');
514 self.state = .in_section;
515 return SectionReader{ .context = self };
516 }
517 }
518 try self.reader.skipUntilDelimiterOrEof('\n');
519 self.state = .in_other;
520 continue;
521 } else {
522 try self.reader.skipUntilDelimiterOrEof('\n');
523 self.state = .in_other;
524 continue;
525 }
526 } else return error.MalformedPEM;
527 },
528 else => return error.MalformedPEM,
529 },
530 .in_other, .in_section => switch (byte) {
531 '#' => {
532 try self.reader.skipUntilDelimiterOrEof('\n');
533 continue;
534 },
535 '\r', '\n', ' ', '\t' => continue,
536 '-' => {
537 if (try self.reader.isBytes("----END ")) {
538 try self.reader.skipUntilDelimiterOrEof('\n');
539 self.state = .none;
540 continue;
541 } else return error.MalformedPEM;
542 },
543 // @TODO Make sure the character is base64
544 else => continue,
545 },
546 }
547 }
548 }
549 };
550}
551
552/// Iterator of io.Reader that each decode one certificate from the PEM reader.
553/// Readers do not have to be fully consumed until end of stream, but they must be
554/// read from in order.
555/// Iterator.SectionReader is the type of the io.Reader, Iterator.NextError is the error
556/// set of the next() function.
557pub fn pemCertificateIterator(reader: anytype) PEMCertificateIterator(@TypeOf(reader)) {
558 return .{ .reader = reader };
559}
560
561pub const NameElement = struct {
562 // Encoded OID without tag
563 oid: asn1.ObjectIdentifier,
564 // Destination buffer
565 buf: []u8,
566 status: enum {
567 not_found,
568 found,
569 errored,
570 },
571};
572
573const github_pem = @embedFile("../test/github.pem");
574const github_der = @embedFile("../test/github.der");
575
576fn expected_pem_certificate_chain(bytes: []const u8, certs: []const []const u8) !void {
577 var fbs = std.io.fixedBufferStream(bytes);
578
579 var it = pemCertificateIterator(fbs.reader());
580 var idx: usize = 0;
581 while (try it.next()) |cert_reader| : (idx += 1) {
582 const result_bytes = try cert_reader.readAllAlloc(std.testing.allocator, std.math.maxInt(usize));
583 defer std.testing.allocator.free(result_bytes);
584 std.testing.expectEqualSlices(u8, certs[idx], result_bytes);
585 }
586 if (idx != certs.len) {
587 std.debug.panic("Read {} certificates, wanted {}", .{ idx, certs.len });
588 }
589 std.testing.expect((try it.next()) == null);
590}
591
592fn expected_pem_certificate(bytes: []const u8, cert_bytes: []const u8) !void {
593 try expected_pem_certificate_chain(bytes, &[1][]const u8{cert_bytes});
594}
595
596test "pemCertificateIterator" {
597 try expected_pem_certificate(github_pem, github_der);
598 try expected_pem_certificate(
599 \\-----BEGIN BOGUS-----
600 \\-----END BOGUS-----
601 \\
602 ++
603 github_pem,
604 github_der,
605 );
606
607 try expected_pem_certificate_chain(
608 github_pem ++
609 \\
610 \\-----BEGIN BOGUS-----
611 \\-----END BOGUS-----
612 \\
613 ++ github_pem,
614 &[2][]const u8{ github_der, github_der },
615 );
616
617 try expected_pem_certificate_chain(
618 \\-----BEGIN BOGUS-----
619 \\-----END BOGUS-----
620 \\
621 ,
622 &[0][]const u8{},
623 );
624
625 // Try reading byte by byte from a cert reader
626 {
627 var fbs = std.io.fixedBufferStream(github_pem ++ "\n# Some comment\n" ++ github_pem);
628 var it = pemCertificateIterator(fbs.reader());
629 // Read a couple of bytes from the first reader, then skip to the next
630 {
631 const first_reader = (try it.next()) orelse return error.NoCertificate;
632 var first_few: [8]u8 = undefined;
633 const bytes = try first_reader.readAll(&first_few);
634 std.testing.expectEqual(first_few.len, bytes);
635 std.testing.expectEqualSlices(u8, github_der[0..bytes], &first_few);
636 }
637
638 const next_reader = (try it.next()) orelse return error.NoCertificate;
639 var idx: usize = 0;
640 while (true) : (idx += 1) {
641 const byte = next_reader.readByte() catch |err| switch (err) {
642 error.EndOfStream => break,
643 else => |e| return e,
644 };
645 if (github_der[idx] != byte) {
646 std.debug.panic("index {}: expected 0x{X}, found 0x{X}", .{ idx, github_der[idx], byte });
647 }
648 }
649 std.testing.expectEqual(github_der.len, idx);
650 std.testing.expect((try it.next()) == null);
651 }
652}
653
654test "TrustAnchorChain" {
655 var fbs = std.io.fixedBufferStream(github_pem ++
656 \\
657 \\# Hellenic Academic and Research Institutions RootCA 2011
658 \\-----BEGIN CERTIFICATE-----
659 \\MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix
660 \\RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
661 \\dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p
662 \\YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw
663 \\NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK
664 \\EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl
665 \\cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
666 \\c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB
667 \\BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz
668 \\dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ
669 \\fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns
670 \\bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD
671 \\75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP
672 \\FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV
673 \\HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp
674 \\5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu
675 \\b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA
676 \\A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p
677 \\6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
678 \\TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7
679 \\dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys
680 \\Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI
681 \\l7WdmplNsDz4SgCbZN2fOUvRJ9e4
682 \\-----END CERTIFICATE-----
683 \\
684 \\# ePKI Root Certification Authority
685 \\-----BEGIN CERTIFICATE-----
686 \\MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
687 \\MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
688 \\ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
689 \\Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
690 \\IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
691 \\SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
692 \\AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
693 \\SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
694 \\ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
695 \\DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
696 \\TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
697 \\fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
698 \\sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
699 \\WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
700 \\nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
701 \\dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
702 \\NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
703 \\AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
704 \\MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
705 \\ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
706 \\uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
707 \\PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
708 \\JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
709 \\gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
710 \\j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
711 \\5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
712 \\o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
713 \\/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
714 \\Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
715 \\W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
716 \\hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
717 \\-----END CERTIFICATE-----
718 );
719 const chain = try TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader());
720 defer chain.deinit();
721}
libs/iguanatls/test/DigiCertGlobalRootCA.crt.pem deleted-22
......@@ -1,22 +0,0 @@
1-----BEGIN CERTIFICATE-----
2MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
3MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
4d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
5QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
6MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
7b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
89w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
9CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
10nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
1143C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
12T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
13gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
14BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
15TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
16DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
17hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
1806O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
19PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
20YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
21CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
22-----END CERTIFICATE-----
libs/iguanatls/test/DigiCertHighAssuranceEVRootCA.crt.pem deleted-23
......@@ -1,23 +0,0 @@
1-----BEGIN CERTIFICATE-----
2MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
3MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
4d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
5ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
6MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
7LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
8RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
9+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
10PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
11xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
12Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
13hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
14EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
15MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
16FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
17nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
18eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
19hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
20Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
21vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
22+OkuE6N36B9K
23-----END CERTIFICATE-----
libs/iguanatls/test/github.der
Binary files a/libs/iguanatls/test/github.der and /dev/null differ
libs/iguanatls/test/github.pem deleted-23
......@@ -1,23 +0,0 @@
1-----BEGIN CERTIFICATE-----
2MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
3MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
4d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
5ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
6MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
7LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
8RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
9+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
10PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
11xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
12Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
13hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
14EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
15MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
16FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
17nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
18eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
19hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
20Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
21vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
22+OkuE6N36B9K
23-----END CERTIFICATE-----
libs/iguanatls/zig.mod deleted-4
......@@ -1,4 +0,0 @@
1id: csbnipaad8n77buaszsnjvlmn6j173fl7pkprsctelswjywe
2name: iguanatls
3main: src/main.zig
4dependencies:
libs/zig-known-folders deleted-1
......@@ -1 +0,0 @@
1Subproject commit e1193f9ef5b3aad7a6071e9f5721934fe04a020e
libs/zuri/.github/workflows/ci.yml deleted-22
......@@ -1,22 +0,0 @@
1name: CI
2on: [push]
3jobs:
4 test:
5 strategy:
6 matrix:
7 os: [ubuntu-latest]
8 runs-on: ${{matrix.os}}
9 steps:
10 - uses: actions/checkout@v2
11 - uses: goto-bus-stop/setup-zig@v1.2.1
12 with:
13 version: master
14 - run: zig build test
15 fmt:
16 runs-on: ubuntu-latest
17 steps:
18 - uses: actions/checkout@v2
19 - uses: goto-bus-stop/setup-zig@v1.2.1
20 with:
21 version: master
22 - run: zig fmt build.zig src --check
\ No newline at end of file
libs/zuri/.gitignore deleted-2
......@@ -1,2 +0,0 @@
1.vscode/
2zig-cache/
\ No newline at end of file
libs/zuri/LICENSE deleted-21
......@@ -1,21 +0,0 @@
1MIT License
2
3Copyright (c) 2019 Vexu
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in all
13copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21SOFTWARE.
\ No newline at end of file
libs/zuri/README.md deleted-16
......@@ -1,16 +0,0 @@
1# zuri
2[URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) parser written in [Zig](https://ziglang.org/).
3
4## Example
5```Zig
6const uri = try Uri.parse("https://ziglang.org/documentation/master/#toc-Introduction");
7assert(mem.eql(u8, uri.scheme, "https"));
8assert(mem.eql(u8, uri.host, "ziglang.org"));
9assert(mem.eql(u8, uri.path, "/documentation/master/"));
10assert(mem.eql(u8, uri.fragment, "toc-Introduction"));
11```
12
13## Zig version
14By default the master branch version requires the latest Zig master version.
15
16For Zig 0.6.0 use commit 89ff8258ccd09d0a4cfda649828cc1c45f2b1f8f.
\ No newline at end of file
libs/zuri/build.zig deleted-10
......@@ -1,10 +0,0 @@
1const Builder = @import("std").build.Builder;
2
3pub fn build(b: *Builder) void {
4 const tests = b.addTest("test.zig");
5 tests.setBuildMode(b.standardReleaseOptions());
6
7 const test_step = b.step("test", "Run library tests");
8 test_step.dependOn(&tests.step);
9 b.default_step.dependOn(test_step);
10}
libs/zuri/src/zuri.zig deleted-592
......@@ -1,592 +0,0 @@
1const std = @import("std");
2const mem = std.mem;
3const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;
5const parseUnsigned = std.fmt.parseUnsigned;
6const net = std.net;
7const expect = std.testing.expect;
8
9const ValueMap = std.StringHashMap([]const u8);
10
11pub const Uri = struct {
12 scheme: []const u8,
13 username: []const u8,
14 password: []const u8,
15 host: Host,
16 port: ?u16,
17 path: []const u8,
18 query: []const u8,
19 fragment: []const u8,
20 len: usize,
21
22 /// possible uri host values
23 pub const Host = union(enum) {
24 ip: net.Address,
25 name: []const u8,
26 };
27
28 /// possible errors for mapQuery
29 pub const MapError = error{
30 NoQuery,
31 OutOfMemory,
32 };
33
34 /// map query string into a hashmap of key value pairs with no value being an empty string
35 pub fn mapQuery(allocator: *Allocator, query: []const u8) MapError!ValueMap {
36 if (query.len == 0) {
37 return error.NoQuery;
38 }
39 var map = ValueMap.init(allocator);
40 errdefer map.deinit();
41 var start: u32 = 0;
42 var mid: u32 = 0;
43 for (query) |c, i| {
44 if (c == ';' or c == '&') {
45 if (mid != 0) {
46 _ = try map.put(query[start..mid], query[mid + 1 .. i]);
47 } else {
48 _ = try map.put(query[start..i], "");
49 }
50 start = @truncate(u32, i + 1);
51 mid = 0;
52 } else if (c == '=') {
53 mid = @truncate(u32, i);
54 }
55 }
56 if (mid != 0) {
57 _ = try map.put(query[start..mid], query[mid + 1 ..]);
58 } else {
59 _ = try map.put(query[start..], "");
60 }
61
62 return map;
63 }
64
65 /// possible errors for decode and encode
66 pub const EncodeError = error{
67 InvalidCharacter,
68 OutOfMemory,
69 };
70
71 /// decode path if it is percent encoded
72 pub fn decode(allocator: *Allocator, path: []const u8) EncodeError!?[]u8 {
73 var ret: ?[]u8 = null;
74 var ret_index: usize = 0;
75 var i: usize = 0;
76
77 while (i < path.len) : (i += 1) {
78 if (path[i] == '%') {
79 if (!isPchar(path[i..])) {
80 return error.InvalidCharacter;
81 }
82 if (ret == null) {
83 ret = try allocator.alloc(u8, path.len);
84 mem.copy(u8, ret.?, path[0..i]);
85 ret_index = i;
86 }
87
88 // charToDigit can't fail because the chars are validated earlier
89 var new = (std.fmt.charToDigit(path[i + 1], 16) catch unreachable) << 4;
90 new |= std.fmt.charToDigit(path[i + 2], 16) catch unreachable;
91 ret.?[ret_index] = new;
92 ret_index += 1;
93 i += 2;
94 } else if (path[i] != '/' and !isPchar(path[i..])) {
95 return error.InvalidCharacter;
96 } else if (ret != null) {
97 ret.?[ret_index] = path[i];
98 ret_index += 1;
99 }
100 }
101 if (ret != null) {
102 return allocator.realloc(ret.?, ret_index) catch ret.?[0..ret_index];
103 }
104 return ret;
105 }
106
107 /// percent encode if path contains characters not allowed in paths
108 pub fn encode(allocator: *Allocator, path: []const u8) EncodeError!?[]u8 {
109 var ret: ?[]u8 = null;
110 var ret_index: usize = 0;
111 for (path) |c, i| {
112 if (c != '/' and !isPchar(path[i..])) {
113 if (ret == null) {
114 ret = try allocator.alloc(u8, path.len * 3);
115 mem.copy(u8, ret.?, path[0..i]);
116 ret_index = i;
117 }
118 const hex_digits = "0123456789ABCDEF";
119 ret.?[ret_index] = '%';
120 ret.?[ret_index + 1] = hex_digits[(c & 0xF0) >> 4];
121 ret.?[ret_index + 2] = hex_digits[c & 0x0F];
122 ret_index += 3;
123 } else if (ret != null) {
124 ret.?[ret_index] = c;
125 ret_index += 1;
126 }
127 }
128 if (ret != null) {
129 return allocator.realloc(ret.?, ret_index) catch ret.?[0..ret_index];
130 }
131 return ret;
132 }
133
134 /// resolves `path`, leaves trailing '/'
135 /// assumes `path` to be valid
136 pub fn resolvePath(allocator: *Allocator, path: []const u8) error{OutOfMemory}![]u8 {
137 assert(path.len > 0);
138 var list = std.ArrayList([]const u8).init(allocator);
139 errdefer list.deinit();
140
141 var it = mem.tokenize(path, "/");
142 while (it.next()) |p| {
143 if (mem.eql(u8, p, ".")) {
144 continue;
145 } else if (mem.eql(u8, p, "..")) {
146 _ = list.popOrNull();
147 } else {
148 try list.append(p);
149 }
150 }
151
152 var buf = try allocator.alloc(u8, path.len);
153 errdefer allocator.free(buf);
154 var len: usize = 0;
155 var segments = list.toOwnedSlice();
156 defer allocator.free(segments);
157
158 for (segments) |s| {
159 buf[len] = '/';
160 len += 1;
161 mem.copy(u8, buf[len..], s);
162 len += s.len;
163 }
164
165 if (path[path.len - 1] == '/') {
166 buf[len] = '/';
167 len += 1;
168 }
169
170 return allocator.realloc(buf, len) catch buf[0..len];
171 }
172
173 /// possible errors for parse
174 pub const Error = error{
175 /// input is not a valid uri due to a invalid character
176 /// mostly a result of invalid ipv6
177 InvalidCharacter,
178
179 /// given input was empty
180 EmptyUri,
181 };
182
183 /// parse URI from input
184 /// empty input is an error
185 /// if assume_auth is true then `example.com` will result in `example.com` being the host instead of path
186 pub fn parse(input: []const u8, assume_auth: bool) Error!Uri {
187 if (input.len == 0) {
188 return error.EmptyUri;
189 }
190 var uri = Uri{
191 .scheme = "",
192 .username = "",
193 .password = "",
194 .host = .{ .name = "" },
195 .port = null,
196 .path = "",
197 .query = "",
198 .fragment = "",
199 .len = 0,
200 };
201
202 switch (input[0]) {
203 'a'...'z', 'A'...'Z' => {
204 uri.parseMaybeScheme(input);
205 },
206 else => {},
207 }
208
209 if (input.len > uri.len + 2 and input[uri.len] == '/' and input[uri.len + 1] == '/') {
210 uri.len += 2; // for the '//'
211 try uri.parseAuth(input[uri.len..]);
212 } else if (assume_auth) {
213 try uri.parseAuth(input[uri.len..]);
214 }
215
216 // make host ip4 address if possible
217 if (uri.host == .name and uri.host.name.len > 0) blk: {
218 var a = net.Address.parseIp4(uri.host.name, 0) catch break :blk;
219 uri.host = .{ .ip = a }; // workaround for https://github.com/ziglang/zig/issues/3234
220 }
221
222 if (uri.host == .ip and uri.port != null) {
223 uri.host.ip.setPort(uri.port.?);
224 }
225
226 uri.parsePath(input[uri.len..]);
227
228 if (input.len > uri.len + 1 and input[uri.len] == '?') {
229 uri.parseQuery(input[uri.len + 1 ..]);
230 }
231
232 if (input.len > uri.len + 1 and input[uri.len] == '#') {
233 uri.parseFragment(input[uri.len + 1 ..]);
234 }
235 return uri;
236 }
237
238 fn parseMaybeScheme(u: *Uri, input: []const u8) void {
239 for (input) |c, i| {
240 switch (c) {
241 'a'...'z', 'A'...'Z', '0'...'9', '+', '-', '.' => {
242 // allowed characters
243 },
244 ':' => {
245 u.scheme = input[0..i];
246 u.len += u.scheme.len + 1; // +1 for the ':'
247 return;
248 },
249 else => {
250 // not a valid scheme
251 return;
252 },
253 }
254 }
255 return;
256 }
257
258 fn parseAuth(u: *Uri, input: []const u8) Error!void {
259 for (input) |c, i| {
260 switch (c) {
261 '@' => {
262 u.username = input[0..i];
263 u.len += i + 1; // +1 for the '@'
264 return u.parseHost(input[i + 1 ..]);
265 },
266 '[' => {
267 if (i != 0)
268 return error.InvalidCharacter;
269 return u.parseIP(input);
270 },
271 ':' => {
272 u.host.name = input[0..i];
273 u.len += i + 1; // +1 for the '@'
274 return u.parseAuthColon(input[i + 1 ..]);
275 },
276 '/', '?', '#' => {
277 u.host.name = input[0..i];
278 u.len += i;
279 return;
280 },
281 else => if (!isPchar(input)) {
282 u.host.name = input[0..i];
283 u.len += input.len;
284 return;
285 },
286 }
287 }
288 u.host.name = input;
289 u.len += input.len;
290 }
291
292 fn parseAuthColon(u: *Uri, input: []const u8) Error!void {
293 for (input) |c, i| {
294 if (c == '@') {
295 u.username = u.host.name;
296 u.password = input[0..i];
297 u.len += i + 1; //1 for the '@'
298 return u.parseHost(input[i + 1 ..]);
299 } else if (c == '/' or c == '?' or c == '#' or !isPchar(input)) {
300 u.port = parseUnsigned(u16, input[0..i], 10) catch return error.InvalidCharacter;
301 u.len += i;
302 return;
303 }
304 }
305 u.port = parseUnsigned(u16, input, 10) catch return error.InvalidCharacter;
306 u.len += input.len;
307 }
308
309 fn parseHost(u: *Uri, input: []const u8) Error!void {
310 for (input) |c, i| {
311 switch (c) {
312 ':' => {
313 u.host.name = input[0..i];
314 u.len += i + 1; // +1 for the ':'
315 return u.parsePort(input[i..]);
316 },
317 '[' => {
318 if (i != 0)
319 return error.InvalidCharacter;
320 return u.parseIP(input);
321 },
322 else => if (c == '/' or c == '?' or c == '#' or !isPchar(input)) {
323 u.host.name = input[0..i];
324 u.len += i;
325 return;
326 },
327 }
328 }
329 u.host.name = input[0..];
330 u.len += input.len;
331 }
332
333 fn parseIP(u: *Uri, input: []const u8) Error!void {
334 const end = mem.indexOfScalar(u8, input, ']') orelse return error.InvalidCharacter;
335 var addr = net.Address.parseIp6(input[1..end], 0) catch return error.InvalidCharacter;
336 u.host = .{ .ip = addr };
337 u.len += end + 1;
338
339 if (input.len > end + 2 and input[end + 1] == ':') {
340 u.len += 1;
341 try u.parsePort(input[end + 2 ..]);
342 }
343 }
344
345 fn parsePort(u: *Uri, input: []const u8) Error!void {
346 for (input) |c, i| {
347 switch (c) {
348 '0'...'9' => {
349 // digits
350 },
351 else => {
352 if (i == 0) return error.InvalidCharacter;
353 u.port = parseUnsigned(u16, input[0..i], 10) catch return error.InvalidCharacter;
354 u.len += i;
355 return;
356 },
357 }
358 }
359 if (input.len == 0) return error.InvalidCharacter;
360 u.port = parseUnsigned(u16, input[0..], 10) catch return error.InvalidCharacter;
361 u.len += input.len;
362 }
363
364 fn parsePath(u: *Uri, input: []const u8) void {
365 for (input) |c, i| {
366 if (c != '/' and (c == '?' or c == '#' or !isPchar(input[i..]))) {
367 u.path = input[0..i];
368 u.len += u.path.len;
369 return;
370 }
371 }
372 u.path = input[0..];
373 u.len += u.path.len;
374 }
375
376 fn parseQuery(u: *Uri, input: []const u8) void {
377 u.len += 1; // +1 for the '?'
378 for (input) |c, i| {
379 if (c == '#' or (c != '/' and c != '?' and !isPchar(input[i..]))) {
380 u.query = input[0..i];
381 u.len += u.query.len;
382 return;
383 }
384 }
385 u.query = input;
386 u.len += input.len;
387 }
388
389 fn parseFragment(u: *Uri, input: []const u8) void {
390 u.len += 1; // +1 for the '#'
391 for (input) |c, i| {
392 if (c != '/' and c != '?' and !isPchar(input[i..])) {
393 u.fragment = input[0..i];
394 u.len += u.fragment.len;
395 return;
396 }
397 }
398 u.fragment = input;
399 u.len += u.fragment.len;
400 }
401
402 /// returns true if str starts with a valid path character or a percent encoded octet
403 pub fn isPchar(str: []const u8) bool {
404 assert(str.len > 0);
405 return switch (str[0]) {
406 'a'...'z', 'A'...'Z', '0'...'9', '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@' => true,
407 '%' => str.len > 3 and isHex(str[1]) and isHex(str[2]),
408 else => false,
409 };
410 }
411
412 /// returns true if c is a hexadecimal digit
413 pub fn isHex(c: u8) bool {
414 return switch (c) {
415 '0'...'9', 'a'...'f', 'A'...'F' => true,
416 else => false,
417 };
418 }
419};
420
421test "basic url" {
422 const uri = try Uri.parse("https://ziglang.org:80/documentation/master/?test#toc-Introduction", false);
423 expect(mem.eql(u8, uri.scheme, "https"));
424 expect(mem.eql(u8, uri.username, ""));
425 expect(mem.eql(u8, uri.password, ""));
426 expect(mem.eql(u8, uri.host.name, "ziglang.org"));
427 expect(uri.port.? == 80);
428 expect(mem.eql(u8, uri.path, "/documentation/master/"));
429 expect(mem.eql(u8, uri.query, "test"));
430 expect(mem.eql(u8, uri.fragment, "toc-Introduction"));
431 expect(uri.len == 66);
432}
433
434test "short" {
435 const uri = try Uri.parse("telnet://192.0.2.16:80/", false);
436 expect(mem.eql(u8, uri.scheme, "telnet"));
437 expect(mem.eql(u8, uri.username, ""));
438 expect(mem.eql(u8, uri.password, ""));
439 var buf = [_]u8{0} ** 100;
440 var ip = std.fmt.bufPrint(buf[0..], "{}", .{uri.host.ip}) catch unreachable;
441 expect(mem.eql(u8, ip, "192.0.2.16:80"));
442 expect(uri.port.? == 80);
443 expect(mem.eql(u8, uri.path, "/"));
444 expect(mem.eql(u8, uri.query, ""));
445 expect(mem.eql(u8, uri.fragment, ""));
446 expect(uri.len == 23);
447}
448
449test "single char" {
450 const uri = try Uri.parse("a", false);
451 expect(mem.eql(u8, uri.scheme, ""));
452 expect(mem.eql(u8, uri.username, ""));
453 expect(mem.eql(u8, uri.password, ""));
454 expect(mem.eql(u8, uri.host.name, ""));
455 expect(uri.port == null);
456 expect(mem.eql(u8, uri.path, "a"));
457 expect(mem.eql(u8, uri.query, ""));
458 expect(mem.eql(u8, uri.fragment, ""));
459 expect(uri.len == 1);
460}
461
462test "ipv6" {
463 const uri = try Uri.parse("ldap://[2001:db8::7]/c=GB?objectClass?one", false);
464 expect(mem.eql(u8, uri.scheme, "ldap"));
465 expect(mem.eql(u8, uri.username, ""));
466 expect(mem.eql(u8, uri.password, ""));
467 var buf = [_]u8{0} ** 100;
468 var ip = std.fmt.bufPrint(buf[0..], "{}", .{uri.host.ip}) catch unreachable;
469 expect(std.mem.eql(u8, ip, "[2001:db8::7]:0"));
470 expect(uri.port == null);
471 expect(mem.eql(u8, uri.path, "/c=GB"));
472 expect(mem.eql(u8, uri.query, "objectClass?one"));
473 expect(mem.eql(u8, uri.fragment, ""));
474 expect(uri.len == 41);
475}
476
477test "mailto" {
478 const uri = try Uri.parse("mailto:John.Doe@example.com", false);
479 expect(mem.eql(u8, uri.scheme, "mailto"));
480 expect(mem.eql(u8, uri.username, ""));
481 expect(mem.eql(u8, uri.password, ""));
482 expect(mem.eql(u8, uri.host.name, ""));
483 expect(uri.port == null);
484 expect(mem.eql(u8, uri.path, "John.Doe@example.com"));
485 expect(mem.eql(u8, uri.query, ""));
486 expect(mem.eql(u8, uri.fragment, ""));
487 expect(uri.len == 27);
488}
489
490test "tel" {
491 const uri = try Uri.parse("tel:+1-816-555-1212", false);
492 expect(mem.eql(u8, uri.scheme, "tel"));
493 expect(mem.eql(u8, uri.username, ""));
494 expect(mem.eql(u8, uri.password, ""));
495 expect(mem.eql(u8, uri.host.name, ""));
496 expect(uri.port == null);
497 expect(mem.eql(u8, uri.path, "+1-816-555-1212"));
498 expect(mem.eql(u8, uri.query, ""));
499 expect(mem.eql(u8, uri.fragment, ""));
500 expect(uri.len == 19);
501}
502
503test "urn" {
504 const uri = try Uri.parse("urn:oasis:names:specification:docbook:dtd:xml:4.1.2", false);
505 expect(mem.eql(u8, uri.scheme, "urn"));
506 expect(mem.eql(u8, uri.username, ""));
507 expect(mem.eql(u8, uri.password, ""));
508 expect(mem.eql(u8, uri.host.name, ""));
509 expect(uri.port == null);
510 expect(mem.eql(u8, uri.path, "oasis:names:specification:docbook:dtd:xml:4.1.2"));
511 expect(mem.eql(u8, uri.query, ""));
512 expect(mem.eql(u8, uri.fragment, ""));
513 expect(uri.len == 51);
514}
515
516test "userinfo" {
517 const uri = try Uri.parse("ftp://username:password@host.com/", false);
518 expect(mem.eql(u8, uri.scheme, "ftp"));
519 expect(mem.eql(u8, uri.username, "username"));
520 expect(mem.eql(u8, uri.password, "password"));
521 expect(mem.eql(u8, uri.host.name, "host.com"));
522 expect(uri.port == null);
523 expect(mem.eql(u8, uri.path, "/"));
524 expect(mem.eql(u8, uri.query, ""));
525 expect(mem.eql(u8, uri.fragment, ""));
526 expect(uri.len == 33);
527}
528
529test "map query" {
530 const uri = try Uri.parse("https://ziglang.org:80/documentation/master/?test;1=true&false#toc-Introduction", false);
531 expect(mem.eql(u8, uri.scheme, "https"));
532 expect(mem.eql(u8, uri.username, ""));
533 expect(mem.eql(u8, uri.password, ""));
534 expect(mem.eql(u8, uri.host.name, "ziglang.org"));
535 expect(uri.port.? == 80);
536 expect(mem.eql(u8, uri.path, "/documentation/master/"));
537 expect(mem.eql(u8, uri.query, "test;1=true&false"));
538 expect(mem.eql(u8, uri.fragment, "toc-Introduction"));
539 var map = try Uri.mapQuery(alloc, uri.query);
540 defer map.deinit();
541 expect(mem.eql(u8, map.get("test").?, ""));
542 expect(mem.eql(u8, map.get("1").?, "true"));
543 expect(mem.eql(u8, map.get("false").?, ""));
544}
545
546test "ends in space" {
547 const uri = try Uri.parse("https://ziglang.org/documentation/master/ something else", false);
548 expect(mem.eql(u8, uri.scheme, "https"));
549 expect(mem.eql(u8, uri.username, ""));
550 expect(mem.eql(u8, uri.password, ""));
551 expect(mem.eql(u8, uri.host.name, "ziglang.org"));
552 expect(mem.eql(u8, uri.path, "/documentation/master/"));
553 expect(uri.len == 41);
554}
555
556test "assume auth" {
557 const uri = try Uri.parse("ziglang.org", true);
558 expect(mem.eql(u8, uri.host.name, "ziglang.org"));
559 expect(uri.len == 11);
560}
561
562var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
563const alloc = &arena.allocator;
564
565test "encode" {
566 const path = (try Uri.encode(alloc, "/안녕하세요.html")).?;
567 expect(mem.eql(u8, path, "/%EC%95%88%EB%85%95%ED%95%98%EC%84%B8%EC%9A%94.html"));
568}
569
570test "decode" {
571 const path = (try Uri.decode(alloc, "/%EC%95%88%EB%85%95%ED%95%98%EC%84%B8%EC%9A%94.html")).?;
572 expect(mem.eql(u8, path, "/안녕하세요.html"));
573}
574
575test "resolvePath" {
576 var a = try Uri.resolvePath(alloc, "/a/b/..");
577 expect(mem.eql(u8, a, "/a"));
578 a = try Uri.resolvePath(alloc, "/a/b/../");
579 expect(mem.eql(u8, a, "/a/"));
580 a = try Uri.resolvePath(alloc, "/a/b/c/../d/../");
581 expect(mem.eql(u8, a, "/a/b/"));
582 a = try Uri.resolvePath(alloc, "/a/b/c/../d/..");
583 expect(mem.eql(u8, a, "/a/b"));
584 a = try Uri.resolvePath(alloc, "/a/b/c/../d/.././");
585 expect(mem.eql(u8, a, "/a/b/"));
586 a = try Uri.resolvePath(alloc, "/a/b/c/../d/../.");
587 expect(mem.eql(u8, a, "/a/b"));
588 a = try Uri.resolvePath(alloc, "/a/../../");
589 expect(mem.eql(u8, a, "/"));
590
591 arena.deinit();
592}
libs/zuri/test.zig deleted-3
......@@ -1,3 +0,0 @@
1comptime {
2 _ = @import("src/zuri.zig");
3}