authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2021-02-07 12:03:28 -08:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2021-02-07 12:03:28 -08:00
log194bee3eba0a55f928dd00fb4fa5d71ddc54aeb6
tree038f8740f72d27f7496e7d648313a27ee69d2679
parent1a6702c54e028a085d8553648e5f8e2604b29cc5

iguanatls: update to 58f72f6


8 files changed, 1612 insertions(+), 418 deletions(-)

libs/iguanatls/bench/bench.zig created+425
......@@ -0,0 +1,425 @@
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 created+33
......@@ -0,0 +1,33 @@
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 created+249
......@@ -0,0 +1,249 @@
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/src/ciphersuites.zig+149-66
......@@ -4,7 +4,11 @@ const mem = std.mem;
44usingnamespace @import("crypto.zig");
55const Chacha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305;
66const Aes128Gcm = std.crypto.aead.aes_gcm.Aes128Gcm;
7const record_length = @import("main.zig").record_length;
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;
812
913pub const suites = struct {
1014 pub const ECDHE_RSA_Chacha20_Poly1305 = struct {
......@@ -106,12 +110,24 @@ pub const suites = struct {
106110 ) !usize {
107111 switch (state.*) {
108112 .none => {
109 const len = (record_length(0x17, reader) catch |err| switch (err) {
113 const tag_length = record_tag_length(reader) catch |err| switch (err) {
110114 error.EndOfStream => return 0,
111115 else => |e| return e,
112 }) - 16;
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 }
113126
114 const curr_bytes = std.math.min(std.math.min(len, buf_size), buffer.len);
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);
115131
116132 var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8);
117133 mem.writeIntBig(u64, nonce[4..12], server_seq.*);
......@@ -119,10 +135,6 @@ pub const suites = struct {
119135 n.* ^= key_data.server_iv(@This())[i];
120136 }
121137
122 // Partially decrypt the data.
123 var encrypted: [buf_size]u8 = undefined;
124 const actually_read = try reader.read(encrypted[0..curr_bytes]);
125
126138 var c: [4]u32 = undefined;
127139 c[0] = 1;
128140 c[1] = mem.readIntLittle(u32, nonce[0..4]);
......@@ -132,32 +144,63 @@ pub const suites = struct {
132144 var context = ChaCha20Stream.initContext(server_key, c);
133145 var idx: usize = 0;
134146 var buf: [64]u8 = undefined;
135 ChaCha20Stream.chacha20Xor(
136 buffer[0..actually_read],
137 encrypted[0..actually_read],
138 server_key,
139 &context,
140 &idx,
141 &buf,
142 );
143 if (actually_read < len) {
144 state.* = .{
145 .in_record = .{
146 .left = len - actually_read,
147 .context = context,
148 .idx = idx,
149 .buf = buf,
150 },
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,
151153 };
152 } else {
153 // @TODO Verify Poly1305.
154 var result: [2] u8 = undefined;
155 ChaCha20Stream.chacha20Xor(
156 &result,
157 &encrypted,
158 server_key,
159 &context,
160 &idx,
161 &buf,
162 );
154163 reader.skipBytes(16, .{}) catch |err| switch (err) {
155 error.EndOfStream => return 0,
164 error.EndOfStream => return error.ServerMalformedResponse,
156165 else => |e| return e,
157166 };
158167 server_seq.* += 1;
159 }
160 return actually_read;
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;
161204 },
162205 .in_record => |*record_info| {
163206 const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);
......@@ -177,7 +220,7 @@ pub const suites = struct {
177220 if (record_info.left == 0) {
178221 // @TODO Verify Poly1305.
179222 reader.skipBytes(16, .{}) catch |err| switch (err) {
180 error.EndOfStream => return 0,
223 error.EndOfStream => return error.ServerMalformedResponse,
181224 else => |e| return e,
182225 };
183226 state.* = .none;
......@@ -293,12 +336,24 @@ pub const suites = struct {
293336 ) !usize {
294337 switch (state.*) {
295338 .none => {
296 const len = (record_length(0x17, reader) catch |err| switch (err) {
339 const tag_length = record_tag_length(reader) catch |err| switch (err) {
297340 error.EndOfStream => return 0,
298341 else => |e| return e,
299 }) - 24;
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 }
300352
301 const curr_bytes = std.math.min(std.math.min(len, buf_size), buffer.len);
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);
302357
303358 var iv: [12]u8 = undefined;
304359 iv[0..4].* = key_data.server_iv(@This()).*;
......@@ -307,10 +362,6 @@ pub const suites = struct {
307362 else => |e| return e,
308363 };
309364
310 // Partially decrypt the data.
311 var encrypted: [buf_size]u8 = undefined;
312 const actually_read = try reader.read(encrypted[0..curr_bytes]);
313
314365 const aes = Aes.initEnc(key_data.server_key(@This()).*);
315366
316367 var j: [16]u8 = undefined;
......@@ -320,34 +371,66 @@ pub const suites = struct {
320371 var counterInt = mem.readInt(u128, &j, .Big);
321372 var idx: usize = 0;
322373
323 ctr(
324 @TypeOf(aes),
325 aes,
326 buffer[0..actually_read],
327 encrypted[0..actually_read],
328 &counterInt,
329 &idx,
330 .Big,
331 );
332
333 if (actually_read < len) {
334 state.* = .{
335 .in_record = .{
336 .left = len - actually_read,
337 .aes = aes,
338 .counterInt = counterInt,
339 .idx = idx,
340 },
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,
341379 };
342 } else {
343 // @TODO Verify the message
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 );
344391 reader.skipBytes(16, .{}) catch |err| switch (err) {
345 error.EndOfStream => return 0,
392 error.EndOfStream => return error.ServerMalformedResponse,
346393 else => |e| return e,
347394 };
348395 server_seq.* += 1;
349 }
350 return actually_read;
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;
351434 },
352435 .in_record => |*record_info| {
353436 const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);
......@@ -368,7 +451,7 @@ pub const suites = struct {
368451 if (record_info.left == 0) {
369452 // @TODO Verify Poly1305.
370453 reader.skipBytes(16, .{}) catch |err| switch (err) {
371 error.EndOfStream => return 0,
454 error.EndOfStream => return error.ServerMalformedResponse,
372455 else => |e| return e,
373456 };
374457 state.* = .none;
......@@ -394,7 +477,7 @@ fn key_field_width(comptime T: type, comptime field: anytype) ?usize {
394477 return @typeInfo(field_info.field_type).Array.len;
395478}
396479
397pub fn key_data_size(comptime ciphersuites: []const type) usize {
480pub fn key_data_size(comptime ciphersuites: anytype) usize {
398481 var max: usize = 0;
399482 for (ciphersuites) |cs| {
400483 const curr = (key_field_width(cs.Keys, .client_mac) orelse 0) +
......@@ -409,7 +492,7 @@ pub fn key_data_size(comptime ciphersuites: []const type) usize {
409492 return max;
410493}
411494
412pub fn KeyData(comptime ciphersuites: []const type) type {
495pub fn KeyData(comptime ciphersuites: anytype) type {
413496 return struct {
414497 data: [key_data_size(ciphersuites)]u8,
415498
......@@ -455,7 +538,7 @@ pub fn KeyData(comptime ciphersuites: []const type) type {
455538}
456539
457540pub fn key_expansion(
458 comptime ciphersuites: []const type,
541 comptime ciphersuites: anytype,
459542 tag: u16,
460543 context: anytype,
461544 comptime next_32_bytes: anytype,
......@@ -505,7 +588,7 @@ pub fn key_expansion(
505588 unreachable;
506589}
507590
508pub fn ClientState(comptime ciphersuites: []const type) type {
591pub fn ClientState(comptime ciphersuites: anytype) type {
509592 var fields: [ciphersuites.len]std.builtin.TypeInfo.UnionField = undefined;
510593 for (ciphersuites) |cs, i| {
511594 fields[i] = .{
......@@ -524,7 +607,7 @@ pub fn ClientState(comptime ciphersuites: []const type) type {
524607 });
525608}
526609
527pub fn client_state_default(comptime ciphersuites: []const type, tag: u16) ClientState(ciphersuites) {
610pub fn client_state_default(comptime ciphersuites: anytype, tag: u16) ClientState(ciphersuites) {
528611 inline for (ciphersuites) |cs| {
529612 if (cs.tag == tag) {
530613 return @unionInit(ClientState(ciphersuites), cs.name, cs.default_state);
libs/iguanatls/src/crypto.zig+48
......@@ -243,6 +243,54 @@ pub const ecc = struct {
243243 }
244244 };
245245
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
246294 fn jacobian_len(comptime Curve: type) usize {
247295 return @divTrunc(Curve.order.len * 8 + 61, 31);
248296 }
libs/iguanatls/src/main.zig+490-146
......@@ -23,6 +23,26 @@ fn handshake_record_length(reader: anytype) !usize {
2323 return try record_length(0x16, reader);
2424}
2525
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
2646pub fn record_length(t: u8, reader: anytype) !usize {
2747 try check_record_type(t, reader);
2848 var record_header: [4]u8 = undefined;
......@@ -72,39 +92,43 @@ fn check_record_type(
7292
7393 const severity = try reader.readByte();
7494 const err_num = try reader.readByte();
75 return switch (err_num) {
76 0 => error.AlertCloseNotify,
77 10 => error.AlertUnexpectedMessage,
78 20 => error.AlertBadRecordMAC,
79 21 => error.AlertDecryptionFailed,
80 22 => error.AlertRecordOverflow,
81 30 => error.AlertDecompressionFailure,
82 40 => error.AlertHandshakeFailure,
83 41 => error.AlertNoCertificate,
84 42 => error.AlertBadCertificate,
85 43 => error.AlertUnsupportedCertificate,
86 44 => error.AlertCertificateRevoked,
87 45 => error.AlertCertificateExpired,
88 46 => error.AlertCertificateUnknown,
89 47 => error.AlertIllegalParameter,
90 48 => error.AlertUnknownCA,
91 49 => error.AlertAccessDenied,
92 50 => error.AlertDecodeError,
93 51 => error.AlertDecryptError,
94 60 => error.AlertExportRestriction,
95 70 => error.AlertProtocolVersion,
96 71 => error.AlertInsufficientSecurity,
97 80 => error.AlertInternalError,
98 90 => error.AlertUserCanceled,
99 100 => error.AlertNoRenegotiation,
100 110 => error.AlertUnsupportedExtension,
101 else => error.ServerMalformedResponse,
102 };
95 return alert_byte_to_error(err_num);
10396 }
10497 if (record_type != expected)
10598 return error.ServerMalformedResponse;
10699}
107100
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
108132fn Sha256Reader(comptime Reader: anytype) type {
109133 const State = struct {
110134 sha256: *Sha256,
......@@ -284,8 +308,39 @@ fn check_cert_timestamp(time: i64, tag_byte: u8, length: usize, reader: anytype)
284308 return error.CertificateVerificationFailed;
285309}
286310
287fn add_cert_subject_dn(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void {
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 {
288333 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);
289344}
290345
291346fn add_cert_public_key(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void {
......@@ -302,8 +357,9 @@ fn add_server_cert(state: *VerifierCaptureState, tag_byte: u8, length: usize, re
302357 const is_ca = state.list.items.len != 0;
303358
304359 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
305362 const cert_bytes = try state.allocator.alloc(u8, length + 1 + encoded_length.len);
306 errdefer state.allocator.free(cert_bytes);
307363 cert_bytes[0] = tag_byte;
308364 mem.copy(u8, cert_bytes[1 .. 1 + encoded_length.len], encoded_length);
309365
......@@ -312,11 +368,11 @@ fn add_server_cert(state: *VerifierCaptureState, tag_byte: u8, length: usize, re
312368 .is_ca = is_ca,
313369 .bytes = cert_bytes,
314370 .dn = undefined,
315 .public_key = undefined,
371 .common_name = &[0]u8{},
372 .public_key = x509.PublicKey.empty,
316373 .signature = asn1.BitString{ .data = &[0]u8{}, .bit_len = 0 },
317374 .signature_algorithm = undefined,
318375 };
319 errdefer state.allocator.free(state.list.items[state.list.items.len - 1].signature.data);
320376
321377 const schema = .{
322378 .sequence,
......@@ -474,7 +530,6 @@ fn verify_signature(
474530 try llmod(&curr_base, modulus);
475531 try curr_exponent.shiftRight(curr_exponent, 1);
476532 }
477 try llmod(&encrypted_signature, modulus);
478533 }
479534 // EMSA-PKCS1-V1_5-ENCODE
480535 if (encrypted_signature.limbs.len * @sizeOf(usize) < signature.data.len)
......@@ -553,6 +608,7 @@ const SignatureAlgorithm = enum {
553608const ServerCertificate = struct {
554609 bytes: []const u8,
555610 dn: []const u8,
611 common_name: []const u8,
556612 public_key: x509.PublicKey,
557613 signature: asn1.BitString,
558614 signature_algorithm: SignatureAlgorithm,
......@@ -566,8 +622,36 @@ const VerifierCaptureState = struct {
566622 fbs: *std.io.FixedBufferStream([]const u8),
567623};
568624
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
569653pub fn default_cert_verifier(
570 allocator: *std.mem.Allocator,
654 allocator: *mem.Allocator,
571655 reader: anytype,
572656 certs_bytes: usize,
573657 trusted_certificates: []const x509.TrustAnchor,
......@@ -622,6 +706,31 @@ pub fn default_cert_verifier(
622706 return error.CertificateVerificationFailed;
623707
624708 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
625734 var i: usize = 0;
626735 while (i < chain.len - 1) : (i += 1) {
627736 if (!try certificate_verify_signature(
......@@ -642,12 +751,7 @@ pub fn default_cert_verifier(
642751 cert.public_key.eql(trusted.public_key))
643752 {
644753 const key = chain[0].public_key;
645 chain[0].public_key = x509.PublicKey{
646 .ec = .{
647 .id = undefined,
648 .curve_point = &[0]u8{},
649 },
650 };
754 chain[0].public_key = x509.PublicKey.empty;
651755 return key;
652756 }
653757
......@@ -662,12 +766,7 @@ pub fn default_cert_verifier(
662766 trusted.public_key,
663767 )) {
664768 const key = chain[0].public_key;
665 chain[0].public_key = x509.PublicKey{
666 .ec = .{
667 .id = undefined,
668 .curve_point = &[0]u8{},
669 },
670 };
769 chain[0].public_key = x509.PublicKey.empty;
671770 return key;
672771 }
673772 }
......@@ -736,6 +835,155 @@ pub fn extract_cert_public_key(allocator: *Allocator, reader: anytype, length: u
736835 return capture_state.pub_key;
737836}
738837
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
739987pub fn client_connect(
740988 options: anytype,
741989 hostname: []const u8,
......@@ -746,7 +994,10 @@ pub fn client_connect(
746994)!Client(
747995 @TypeOf(options.reader),
748996 @TypeOf(options.writer),
749 options.ciphersuites,
997 if (@hasField(@TypeOf(options), "ciphersuites"))
998 options.ciphersuites
999 else
1000 ciphersuites.all,
7501001 @hasField(@TypeOf(options), "protocols"),
7511002) {
7521003 const Options = @TypeOf(options);
......@@ -761,6 +1012,20 @@ pub fn client_connect(
7611012 @compileError("Option tuple is missing field 'trusted_certificates' for .default cert_verifier");
7621013 }
7631014
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
7641029 const has_alpn = comptime @hasField(Options, "protocols");
7651030 var handshake_record_hash = Sha256.init(.{});
7661031 const reader = options.reader;
......@@ -777,11 +1042,7 @@ pub fn client_connect(
7771042 rand.bytes(&client_random);
7781043
7791044 var server_random: [32]u8 = undefined;
780
781 if (options.ciphersuites.len == 0)
782 @compileError("Must provide at least one ciphersuite.");
783 const ciphersuite_bytes = 2 * options.ciphersuites.len + 2;
784 // @TODO Make sure the individual lengths are u16s
1045 const ciphersuite_bytes = 2 * suites.len + 2;
7851046 const alpn_bytes = if (has_alpn) blk: {
7861047 var sum: usize = 0;
7871048 for (options.protocols) |proto| {
......@@ -789,6 +1050,7 @@ pub fn client_connect(
7891050 }
7901051 break :blk 6 + options.protocols.len + sum;
7911052 } else 0;
1053 const curvelist_bytes = 2 * curvelist.len;
7921054 var protocol: if (has_alpn) []const u8 else void = undefined;
7931055 {
7941056 const client_hello_start = comptime blk: {
......@@ -810,7 +1072,7 @@ pub fn client_connect(
8101072 // Same as above, couldnt achieve this with a single buffer.
8111073 // TLS_EMPTY_RENEGOTIATION_INFO_SCSV
8121074 var ciphersuite_buf: []const u8 = &[2]u8{ 0x00, 0x0f };
813 for (options.ciphersuites) |cs, i| {
1075 for (suites) |cs, i| {
8141076 // Also check for properties of the ciphersuites here
8151077 if (cs.key_exchange != .ecdhe)
8161078 @compileError("Non ECDHE key exchange is not supported yet.");
......@@ -838,10 +1100,10 @@ pub fn client_connect(
8381100 };
8391101
8401102 var msg_buf = client_hello_start.ptr[0..client_hello_start.len].*;
841 mem.writeIntBig(u16, msg_buf[3..5], @intCast(u16, alpn_bytes + hostname.len + 0x59 + ciphersuite_bytes));
842 mem.writeIntBig(u24, msg_buf[6..9], @intCast(u24, alpn_bytes + hostname.len + 0x55 + ciphersuite_bytes));
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));
8431105 mem.copy(u8, msg_buf[11..43], &client_random);
844 mem.writeIntBig(u16, msg_buf[48 + ciphersuite_bytes ..][0..2], @intCast(u16, alpn_bytes + hostname.len + 0x2C));
1106 mem.writeIntBig(u16, msg_buf[48 + ciphersuite_bytes ..][0..2], @intCast(u16, alpn_bytes + hostname.len + 0x28 + curvelist_bytes));
8451107 mem.writeIntBig(u16, msg_buf[52 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 5));
8461108 mem.writeIntBig(u16, msg_buf[54 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 3));
8471109 mem.writeIntBig(u16, msg_buf[57 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len));
......@@ -849,7 +1111,6 @@ pub fn client_connect(
8491111 try hashing_writer.writeAll(msg_buf[5..]);
8501112 }
8511113 try hashing_writer.writeAll(hostname);
852 // @TODO Fix this with wikipedia test, add secp384r1 support (then app options.curves but default to all when not there (also do this for ciphersuites))
8531114 if (has_alpn) {
8541115 var msg_buf = [6]u8{ 0x00, 0x10, undefined, undefined, undefined, undefined };
8551116 mem.writeIntBig(u16, msg_buf[2..4], @intCast(u16, alpn_bytes - 4));
......@@ -860,20 +1121,37 @@ pub fn client_connect(
8601121 try hashing_writer.writeAll(proto);
8611122 }
8621123 }
863 try hashing_writer.writeAll(&[35]u8{
864 // Extension: supported groups, for now just x25519 (00 1D) and secp384r1 (00 0x18)
865 0x00, 0x0A, 0x00, 0x06, 0x00, 0x04, 0x00, 0x1D, 0x00, 0x18,
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{
8661143 // Extension: EC point formats => uncompressed point format
8671144 0x00, 0x0B, 0x00, 0x02, 0x01, 0x00,
8681145 // Extension: Signature algorithms
8691146 // RSA/PKCS1/SHA256, RSA/PKCS1/SHA512
870 0x00, 0x0D, 0x00, 0x06,
871 0x00, 0x04, 0x04, 0x01, 0x06, 0x01,
1147 0x00, 0x0D, 0x00, 0x06, 0x00, 0x04,
1148 0x04, 0x01, 0x06, 0x01,
8721149 // Extension: Renegotiation Info => new connection
873 0xFF, 0x01, 0x00, 0x01,
874 0x00,
1150 0xFF, 0x01,
1151 0x00, 0x01, 0x00,
8751152 // Extension: SCT (signed certificate timestamp)
876 0x00, 0x12, 0x00, 0x00,
1153 0x00, 0x12, 0x00,
1154 0x00,
8771155 });
8781156
8791157 // Read server hello
......@@ -900,7 +1178,7 @@ pub fn client_connect(
9001178 {
9011179 ciphersuite = try hashing_reader.readIntBig(u16);
9021180 var found = false;
903 inline for (options.ciphersuites) |cs| {
1181 inline for (suites) |cs| {
9041182 if (ciphersuite == cs.tag) {
9051183 found = true;
9061184 // TODO This segfaults stage1
......@@ -1002,8 +1280,10 @@ pub fn client_connect(
10021280 }
10031281 errdefer certificate_public_key.deinit(options.temp_allocator);
10041282 // Read server ephemeral public key
1005 var server_public_key_buf: [97]u8 = undefined;
1006 var curve_id: enum { x25519, secp384r1 } = undefined;
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;
10071287 {
10081288 const length = try handshake_record_length(reader);
10091289 {
......@@ -1012,24 +1292,38 @@ pub fn client_connect(
10121292 if (handshake_header[0] != 0x0c)
10131293 return error.ServerMalformedResponse;
10141294
1015 // Only x25519 and secp384r1 supported for now.
1016 var curve_bytes: [3]u8 = undefined;
1017 try hashing_reader.readNoEof(&curve_bytes);
1018 curve_id = if (mem.eql(u8, &curve_bytes, "\x03\x00\x1D"))
1019 .x25519
1020 else if (mem.eql(u8, &curve_bytes, "\x03\x00\x18"))
1021 .secp384r1
1022 else
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)
10231309 return error.ServerInvalidCurve;
10241310 }
10251311
1026 const pub_key_len = try hashing_reader.readByte();
1027 if ((curve_id == .x25519 and pub_key_len != 32) or
1028 (curve_id == .secp384r1 and pub_key_len != 97))
1029 return error.ServerMalformedResponse;
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
10301322 try hashing_reader.readNoEof(server_public_key_buf[0..pub_key_len]);
1031 if (curve_id == .secp384r1 and server_public_key_buf[0] != 0x04)
1032 return error.ServerMalformedResponse;
1323 if (curve_id != curves.x25519.tag) {
1324 if (server_public_key_buf[0] != 0x04)
1325 return error.ServerMalformedResponse;
1326 }
10331327
10341328 // Signed public key
10351329 const signature_id = try hashing_reader.readIntBig(u16);
......@@ -1043,10 +1337,9 @@ pub fn client_connect(
10431337 var sha256 = Sha256.init(.{});
10441338 sha256.update(&client_random);
10451339 sha256.update(&server_random);
1046 switch (curve_id) {
1047 .x25519 => sha256.update("\x03\x00\x1D\x20"),
1048 .secp384r1 => sha256.update("\x03\x00\x18\x20"),
1049 }
1340 sha256.update(&curve_id_buf);
1341 // @TODO Should this always be \x20 instead?
1342 sha256.update(&[1]u8{pub_key_len});
10501343 sha256.update(server_public_key_buf[0..pub_key_len]);
10511344 sha256.final(hash_buf[0..32]);
10521345 hash = hash_buf[0..32];
......@@ -1057,10 +1350,7 @@ pub fn client_connect(
10571350 var sha512 = Sha512.init(.{});
10581351 sha512.update(&client_random);
10591352 sha512.update(&server_random);
1060 switch (curve_id) {
1061 .x25519 => sha512.update("\x03\x00\x1D"),
1062 .secp384r1 => sha512.update("\x03\x00\x18"),
1063 }
1353 sha512.update(&curve_id_buf);
10641354 sha512.update(&[1]u8{pub_key_len});
10651355 sha512.update(server_public_key_buf[0..pub_key_len]);
10661356 sha512.final(hash_buf[0..64]);
......@@ -1083,7 +1373,7 @@ pub fn client_connect(
10831373 return error.ServerInvalidSignature;
10841374
10851375 certificate_public_key.deinit(options.temp_allocator);
1086 certificate_public_key = x509.PublicKey{ .ec = .{ .id = undefined, .curve_point = &[0]u8{} } };
1376 certificate_public_key = x509.PublicKey.empty;
10871377 }
10881378 // Read server hello done
10891379 {
......@@ -1094,60 +1384,38 @@ pub fn client_connect(
10941384 }
10951385
10961386 // Generate keys for the session
1097 const client_key_pair: extern union {
1098 x25519: std.crypto.dh.X25519.KeyPair,
1099 secp384r1: crypto.ecc.KeyPair(crypto.ecc.SECP384R1),
1100 } = switch (curve_id) {
1101 .x25519 => while (true) {
1102 var seed: [32]u8 = undefined;
1103 rand.bytes(&seed);
1104 const tmp = std.crypto.dh.X25519.KeyPair.create(seed) catch continue;
1105 break .{ .x25519 = tmp };
1106 } else unreachable,
1107 .secp384r1 => blk: {
1108 var seed: [48]u8 = undefined;
1109 rand.bytes(&seed);
1110 break :blk .{ .secp384r1 = crypto.ecc.make_key_pair(crypto.ecc.SECP384R1, seed) };
1111 },
1112 };
1387 const client_key_pair = curves.make_key_pair(curvelist, curve_id, rand);
11131388
11141389 // Client key exchange
1115 switch (curve_id) {
1116 .x25519 => {
1117 try writer.writeAll(&[5]u8{ 0x16, 0x03, 0x03, 0x00, 0x25 });
1118 try hashing_writer.writeAll(&[5]u8{ 0x10, 0x00, 0x00, 0x21, 0x20 });
1119 try hashing_writer.writeAll(&client_key_pair.x25519.public_key);
1120 },
1121 .secp384r1 => {
1122 try writer.writeAll(&[5]u8{ 0x16, 0x03, 0x03, 0x00, 0x66 });
1123 try hashing_writer.writeAll(&[6]u8{ 0x10, 0x00, 0x00, 0x62, 0x61, 0x04 });
1124 try hashing_writer.writeAll(&client_key_pair.secp384r1.public_key);
1125 },
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 }
11261405 }
1406
11271407 // Client encryption keys calculation for ECDHE_RSA cipher suites with SHA256 hash
11281408 var master_secret: [48]u8 = undefined;
1129 var key_data: ciphers.KeyData(options.ciphersuites) = undefined;
1409 var key_data: ciphers.KeyData(suites) = undefined;
11301410 {
1131 var pre_master_secret_buf: [96]u8 = undefined;
1132 const pre_master_secret = switch (curve_id) {
1133 .x25519 => blk: {
1134 pre_master_secret_buf[0..32].* = std.crypto.dh.X25519.scalarmult(
1135 client_key_pair.x25519.secret_key,
1136 server_public_key_buf[0..32].*,
1137 ) catch
1138 return error.PreMasterGenerationFailed;
1139 break :blk pre_master_secret_buf[0..32];
1140 },
1141 .secp384r1 => blk: {
1142 pre_master_secret_buf = crypto.ecc.scalarmult(
1143 crypto.ecc.SECP384R1,
1144 server_public_key_buf[1..].*,
1145 &client_key_pair.secp384r1.secret_key,
1146 ) catch
1147 return error.PreMasterGenerationFailed;
1148 break :blk pre_master_secret_buf[0..48];
1149 },
1150 };
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 );
11511419
11521420 var seed: [77]u8 = undefined;
11531421 seed[0..13].* = "master secret".*;
......@@ -1209,7 +1477,7 @@ pub fn client_connect(
12091477 .master_secret = &master_secret,
12101478 };
12111479
1212 key_data = ciphers.key_expansion(options.ciphersuites, ciphersuite, &state, next_32_bytes);
1480 key_data = ciphers.key_expansion(suites, ciphersuite, &state, next_32_bytes);
12131481 }
12141482
12151483 // Client change cipher spec and client handshake finished
......@@ -1245,7 +1513,7 @@ pub fn client_connect(
12451513 }
12461514 handshake_record_hash.update(&verify_message);
12471515
1248 inline for (options.ciphersuites) |cs| {
1516 inline for (suites) |cs| {
12491517 if (cs.tag == ciphersuite) {
12501518 try cs.raw_write(
12511519 256,
......@@ -1285,7 +1553,7 @@ pub fn client_connect(
12851553 verify_message[4..16].* = p1[0..12].*;
12861554 }
12871555
1288 inline for (options.ciphersuites) |cs| {
1556 inline for (suites) |cs| {
12891557 if (cs.tag == ciphersuite) {
12901558 if (!try cs.check_verify_message(&key_data, length, reader, verify_message))
12911559 return error.ServerInvalidVerifyData;
......@@ -1293,10 +1561,10 @@ pub fn client_connect(
12931561 }
12941562 }
12951563
1296 return Client(@TypeOf(reader), @TypeOf(writer), options.ciphersuites, has_alpn){
1564 return Client(@TypeOf(reader), @TypeOf(writer), suites, has_alpn){
12971565 .ciphersuite = ciphersuite,
12981566 .key_data = key_data,
1299 .state = ciphers.client_state_default(options.ciphersuites, ciphersuite),
1567 .state = ciphers.client_state_default(suites, ciphersuite),
13001568 .rand = rand,
13011569 .parent_reader = reader,
13021570 .parent_writer = writer,
......@@ -1418,8 +1686,9 @@ test "HTTPS request on wikipedia main page" {
14181686 .cert_verifier = .default,
14191687 .temp_allocator = std.testing.allocator,
14201688 .trusted_certificates = trusted_chain.data.items,
1421 .ciphersuites = ciphersuites.all,
1689 .ciphersuites = .{ciphersuites.ECDHE_RSA_Chacha20_Poly1305},
14221690 .protocols = &[_][]const u8{"http/1.1"},
1691 .curves = .{curves.x25519},
14231692 }, "en.wikipedia.org");
14241693 defer client.close_notify() catch {};
14251694
......@@ -1471,9 +1740,9 @@ test "HTTPS request on twitch oath2 endpoint" {
14711740 .reader = sock.reader(),
14721741 .writer = sock.writer(),
14731742 .cert_verifier = .none,
1474 .ciphersuites = ciphersuites.all,
14751743 .protocols = &[_][]const u8{"http/1.1"},
14761744 }, "id.twitch.tv");
1745 std.testing.expectEqualStrings("http/1.1", client.protocol);
14771746 defer client.close_notify() catch {};
14781747
14791748 try client.writer().writeAll("GET /oauth2/validate HTTP/1.1\r\nHost: id.twitch.tv\r\nAccept: */*\r\n\r\n");
......@@ -1497,3 +1766,78 @@ test "HTTPS request on twitch oath2 endpoint" {
14971766
14981767 try client.reader().readNoEof(html_contents);
14991768}
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+196-206
......@@ -21,6 +21,8 @@ pub const CurveId = enum {
2121// zig fmt: on
2222
2323pub const PublicKey = union(enum) {
24 pub const empty = PublicKey{ .ec = .{ .id = undefined, .curve_point = &[0]u8{} } };
25
2426 /// RSA public key
2527 rsa: struct {
2628 //Positive std.math.big.int.Const numbers.
......@@ -45,7 +47,7 @@ pub const PublicKey = union(enum) {
4547 }
4648
4749 pub fn eql(self: @This(), other: @This()) bool {
48 if (@as(@TagType(@This()), self) != @as(@TagType(@This()), other))
50 if (@as(std.meta.Tag(@This()), self) != @as(std.meta.Tag(@This()), other))
4951 return false;
5052 switch (self) {
5153 .rsa => |mod_exp| return mem.eql(usize, mod_exp.exponent, other.rsa.exponent) and
......@@ -63,7 +65,7 @@ pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey {
6365 if ((try reader.readByte()) != 0x06)
6466 return error.MalformedDER;
6567 const oid_bytes = try asn1.der.parse_length(reader);
66 if (oid_bytes == 9 ) {
68 if (oid_bytes == 9) {
6769 // @TODO This fails in async if merged with the if
6870 if (!try reader.isBytes(&[9]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1 }))
6971 return error.MalformedDER;
......@@ -127,16 +129,15 @@ pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey {
127129 // 1.3.132.0.{34, 35}
128130 const last_byte = try reader.readByte();
129131 if (last_byte == 0x22)
130 key.ec = .{ .id = .secp384r1, .curve_point = undefined }
132 key = .{ .ec = .{ .id = .secp384r1, .curve_point = undefined } }
131133 else if (last_byte == 0x23)
132 key.ec = .{ .id = .secp521r1, .curve_point = undefined }
134 key = .{ .ec = .{ .id = .secp521r1, .curve_point = undefined } }
133135 else
134136 return error.MalformedDER;
135 } else if (curve_oid_bytes == 8)
136 {
137 } else if (curve_oid_bytes == 8) {
137138 if (!try reader.isBytes(&[8]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x3, 0x1, 0x7 }))
138139 return error.MalformedDER;
139 key.ec = .{ .id = .secp256r1, .curve_point = undefined };
140 key = .{ .ec = .{ .id = .secp256r1, .curve_point = undefined } };
140141 } else {
141142 return error.MalformedDER;
142143 }
......@@ -150,7 +151,7 @@ pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey {
150151 return error.MalformedDER;
151152 const bit_memory = try allocator.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable);
152153 errdefer allocator.free(bit_memory);
153 try reader.readNoEof(bit_memory[0.. byte_len - 1]);
154 try reader.readNoEof(bit_memory[0 .. byte_len - 1]);
154155
155156 key.ec.curve_point = bit_memory;
156157 return key;
......@@ -199,14 +200,21 @@ pub const TrustAnchor = struct {
199200
200201 const data = object_id.object_identifier.data;
201202 // Basic constraints extension
202 if (data[0] != 2 or data[1] != 5 or data[2] != 29 or data[3] != 15)
203 if (data[0] != 2 or data[1] != 5 or data[2] != 29 or data[3] != 19)
203204 return;
204205
205206 const basic_constraints = try asn1.der.parse_value(state.allocator, reader);
206207 defer basic_constraints.deinit(state.allocator);
207 if (basic_constraints != .bool)
208 return error.DoesNotMatchSchema;
209 state.self.is_ca = basic_constraints.bool;
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 }
210218 }
211219
212220 fn initExtensions(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
......@@ -344,17 +352,7 @@ pub const TrustAnchorChain = struct {
344352 var buffered = std.io.bufferedReader(cert_reader);
345353 const anchor = try TrustAnchor.create(allocator, buffered.reader());
346354 errdefer anchor.deinit(allocator);
347
348 // This read forces the cert reader to find the `-----END`
349 // @TODO Should work without this read, investigate
350 _ = cert_reader.readByte() catch |err| switch (err) {
351 error.EndOfStream => {
352 try self.data.append(anchor);
353 break;
354 },
355 else => |e| return e,
356 };
357 return error.MalformedDER;
355 try self.data.append(anchor);
358356 }
359357 return self;
360358 }
......@@ -367,75 +365,54 @@ pub const TrustAnchorChain = struct {
367365};
368366
369367fn PEMSectionReader(comptime Reader: type) type {
370 const ReadError = Reader.Error || error{MalformedPEM};
371 const S = struct {
372 pub fn read(self: *PEMCertificateIterator(Reader), buffer: []u8) ReadError!usize {
373 const end = "-----END ";
374 var end_letters_matched: ?usize = null;
375
368 const Error = Reader.Error || error{MalformedPEM};
369 const read = struct {
370 fn f(it: *PEMCertificateIterator(Reader), buf: []u8) Error!usize {
376371 var out_idx: usize = 0;
377 if (self.waiting_chars_len > 0) {
378 const rest_written = std.math.min(self.waiting_chars_len, buffer.len);
372 if (it.waiting_chars_len > 0) {
373 const rest_written = std.math.min(it.waiting_chars_len, buf.len);
379374 while (out_idx < rest_written) : (out_idx += 1) {
380 buffer[out_idx] = self.waiting_chars[out_idx];
375 buf[out_idx] = it.waiting_chars[out_idx];
381376 }
382377
383 self.waiting_chars_len -= rest_written;
384 if (self.waiting_chars_len != 0) {
385 std.mem.copy(u8, self.waiting_chars[0..], self.waiting_chars[rest_written..]);
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..]);
386381 }
387382
388 if (out_idx == buffer.len) {
383 if (out_idx == buf.len) {
389384 return out_idx;
390385 }
391386 }
387 if (it.state != .in_section)
388 return out_idx;
392389
393390 var base64_buf: [4]u8 = undefined;
394391 var base64_idx: usize = 0;
395
396392 while (true) {
397 var byte = self.reader.readByte() catch |err| switch (err) {
398 error.EndOfStream => {
399 if (self.skip_to_newline_exit) {
400 self.state = .none;
401 return 0;
402 }
403 return error.MalformedPEM;
404 },
393 const byte = it.reader.readByte() catch |err| switch (err) {
394 error.EndOfStream => return out_idx,
405395 else => |e| return e,
406396 };
407397
408 if (self.skip_to_newline_exit) {
409 if (byte == '\n') {
410 self.skip_to_newline_exit = false;
411 self.state = .none;
412 return 0;
413 }
414 continue;
415 }
416
417 if (byte == '\n' or byte == '\r') {
418 self.empty_line = true;
419 continue;
420 }
421 defer self.empty_line = false;
422
423 if (end_letters_matched) |*matched| {
424 if (end[matched.*] == byte) {
425 matched.* += 1;
426 if (matched.* == end.len) {
427 self.skip_to_newline_exit = true;
428 if (out_idx > 0)
429 return out_idx
430 else
431 continue;
432 }
433 continue;
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;
434406 } else return error.MalformedPEM;
435 } else if (self.empty_line and end[0] == byte) {
436 end_letters_matched = 1;
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')
437415 continue;
438 }
439416
440417 base64_buf[base64_idx] = byte;
441418 base64_idx += 1;
......@@ -444,8 +421,9 @@ fn PEMSectionReader(comptime Reader: type) type {
444421
445422 const out_len = std.base64.standard_decoder.calcSize(&base64_buf) catch
446423 return error.MalformedPEM;
447 const rest_chars = if (out_len > buffer.len - out_idx)
448 out_len - (buffer.len - out_idx)
424
425 const rest_chars = if (out_len > buf.len - out_idx)
426 out_len - (buf.len - out_idx)
449427 else
450428 0;
451429 const buf_chars = out_len - rest_chars;
......@@ -455,21 +433,26 @@ fn PEMSectionReader(comptime Reader: type) type {
455433
456434 var i: u3 = 0;
457435 while (i < buf_chars) : (i += 1) {
458 buffer[out_idx] = res_buffer[i];
436 buf[out_idx] = res_buffer[i];
459437 out_idx += 1;
460438 }
461439
462440 if (rest_chars > 0) {
463 mem.copy(u8, &self.waiting_chars, res_buffer[i..]);
464 self.waiting_chars_len = @intCast(u2, rest_chars);
441 mem.copy(u8, &it.waiting_chars, res_buffer[i..]);
442 it.waiting_chars_len = @intCast(u2, rest_chars);
465443 }
466 if (out_idx == buffer.len)
444 if (out_idx == buf.len)
467445 return out_idx;
468446 }
469447 }
470448 }
471 };
472 return std.io.Reader(*PEMCertificateIterator(Reader), ReadError, S.read);
449 }.f;
450
451 return std.io.Reader(
452 *PEMCertificateIterator(Reader),
453 Error,
454 read,
455 );
473456}
474457
475458fn PEMCertificateIterator(comptime Reader: type) type {
......@@ -479,141 +462,86 @@ fn PEMCertificateIterator(comptime Reader: type) type {
479462
480463 reader: Reader,
481464 // Internal state for the iterator and the current reader.
482 skip_to_newline_exit: bool = false,
483 empty_line: bool = false,
484 waiting_chars: [4]u8 = undefined,
485 waiting_chars_len: u2 = 0,
486465 state: enum {
487466 none,
488 in_section_name,
489 in_cert,
490467 in_other,
468 in_section,
491469 } = .none,
470 waiting_chars: [4]u8 = undefined,
471 waiting_chars_len: u2 = 0,
492472
473 // @TODO More verification, this will accept lots of invalid PEM
493474 pub fn next(self: *@This()) NextError!?SectionReader {
494 const end = "-----END ";
495 const begin = "-----BEGIN ";
496 const certificate = "CERTIFICATE";
497 const x509_certificate = "X.509 CERTIFICATE";
498
499 var line_empty = true;
500 var end_letters_matched: ?usize = null;
501 var begin_letters_matched: ?usize = null;
502 var certificate_letters_matched: ?usize = null;
503 var x509_certificate_letters_matched: ?usize = null;
504 var skip_to_newline = false;
505 var return_after_skip = false;
506
507 var base64_buf: [4]u8 = undefined;
508 var base64_buf_idx: usize = 0;
509
510 // Called next before reading all of the previous cert.
511 if (self.state == .in_cert) {
512 self.waiting_chars_len = 0;
513 }
514
475 self.waiting_chars_len = 0;
515476 while (true) {
516 var last_byte = false;
517477 const byte = self.reader.readByte() catch |err| switch (err) {
518 error.EndOfStream => blk: {
519 if (line_empty and self.state == .none) {
520 return null;
521 } else {
522 last_byte = true;
523 break :blk '\n';
524 }
525 },
478 error.EndOfStream => if (self.state == .none)
479 return null
480 else
481 return error.EndOfStream,
526482 else => |e| return e,
527483 };
528484
529 if (skip_to_newline) {
530 if (last_byte)
531 return null;
532 if (byte == '\n') {
533 if (return_after_skip) {
534 return SectionReader{ .context = self };
535 }
536 skip_to_newline = false;
537 line_empty = true;
538 }
539 continue;
540 } else if (byte == '\r' or byte == '\n') {
541 line_empty = true;
542 continue;
543 }
544
545 defer line_empty = byte == '\n' or (line_empty and byte == ' ');
546
547485 switch (self.state) {
548 .none => {
549 if (begin_letters_matched) |*matched| {
550 if (begin[matched.*] != byte)
551 return error.MalformedPEM;
552
553 matched.* += 1;
554 if (matched.* == begin.len) {
555 self.state = .in_section_name;
556 line_empty = true;
557 begin_letters_matched = null;
558 }
559 } else if (begin[0] == byte) {
560 begin_letters_matched = 1;
561 } else if (mem.indexOfScalar(u8, &std.ascii.spaces, byte) != null) {
562 if (last_byte) return null;
563 } else return error.MalformedPEM;
564 },
565 .in_section_name => {
566 if (certificate_letters_matched) |*matched| {
567 if (certificate[matched.*] != byte) {
568 self.state = .in_other;
569 skip_to_newline = true;
570 continue;
571 }
572 matched.* += 1;
573 if (matched.* == certificate.len) {
574 self.state = .in_cert;
575 certificate_letters_matched = null;
576 skip_to_newline = true;
577 return_after_skip = true;
578 }
579 } else if (x509_certificate_letters_matched) |*matched| {
580 if (x509_certificate[matched.*] != byte) {
581 self.state = .in_other;
582 skip_to_newline = true;
583 continue;
584 }
585 matched.* += 1;
586 if (matched.* == x509_certificate.len) {
587 self.state = .in_cert;
588 x509_certificate_letters_matched = null;
589 skip_to_newline = true;
590 return_after_skip = true;
591 }
592 } else if (line_empty and certificate[0] == byte) {
593 certificate_letters_matched = 1;
594 } else if (line_empty and x509_certificate[0] == byte) {
595 x509_certificate_letters_matched = 1;
596 } else if (line_empty) {
597 self.state = .in_other;
598 skip_to_newline = true;
599 } else unreachable;
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,
600529 },
601 .in_other, .in_cert => {
602 if (end_letters_matched) |*matched| {
603 if (end[matched.*] != byte) {
604 end_letters_matched = null;
605 skip_to_newline = true;
606 continue;
607 }
608 matched.* += 1;
609 if (matched.* == end.len) {
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');
610539 self.state = .none;
611 end_letters_matched = null;
612 skip_to_newline = true;
613 }
614 } else if (line_empty and end[0] == byte) {
615 end_letters_matched = 1;
616 }
540 continue;
541 } else return error.MalformedPEM;
542 },
543 // @TODO Make sure the character is base64
544 else => continue,
617545 },
618546 }
619547 }
......@@ -696,9 +624,8 @@ test "pemCertificateIterator" {
696624
697625 // Try reading byte by byte from a cert reader
698626 {
699 var fbs = std.io.fixedBufferStream(github_pem ++ "\n" ++ github_pem);
627 var fbs = std.io.fixedBufferStream(github_pem ++ "\n# Some comment\n" ++ github_pem);
700628 var it = pemCertificateIterator(fbs.reader());
701
702629 // Read a couple of bytes from the first reader, then skip to the next
703630 {
704631 const first_reader = (try it.next()) orelse return error.NoCertificate;
......@@ -725,7 +652,70 @@ test "pemCertificateIterator" {
725652}
726653
727654test "TrustAnchorChain" {
728 var fbs = std.io.fixedBufferStream(github_pem);
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 );
729719 const chain = try TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader());
730720 defer chain.deinit();
731721}
libs/iguanatls/test/DigiCertGlobalRootCA.crt.pem created+22
......@@ -0,0 +1,22 @@
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-----