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;...@@ -4,7 +4,11 @@ const mem = std.mem;
4usingnamespace @import("crypto.zig");4usingnamespace @import("crypto.zig");
5const Chacha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305;5const Chacha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305;
6const Aes128Gcm = std.crypto.aead.aes_gcm.Aes128Gcm;6const 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
9pub const suites = struct {13pub const suites = struct {
10 pub const ECDHE_RSA_Chacha20_Poly1305 = struct {14 pub const ECDHE_RSA_Chacha20_Poly1305 = struct {
...@@ -106,12 +110,24 @@ pub const suites = struct {...@@ -106,12 +110,24 @@ pub const suites = struct {
106 ) !usize {110 ) !usize {
107 switch (state.*) {111 switch (state.*) {
108 .none => {112 .none => {
109 const len = (record_length(0x17, reader) catch |err| switch (err) {113 const tag_length = record_tag_length(reader) catch |err| switch (err) {
110 error.EndOfStream => return 0,114 error.EndOfStream => return 0,
111 else => |e| return e,115 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
116 var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8);132 var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8);
117 mem.writeIntBig(u64, nonce[4..12], server_seq.*);133 mem.writeIntBig(u64, nonce[4..12], server_seq.*);
...@@ -119,10 +135,6 @@ pub const suites = struct {...@@ -119,10 +135,6 @@ pub const suites = struct {
119 n.* ^= key_data.server_iv(@This())[i];135 n.* ^= key_data.server_iv(@This())[i];
120 }136 }
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
126 var c: [4]u32 = undefined;138 var c: [4]u32 = undefined;
127 c[0] = 1;139 c[0] = 1;
128 c[1] = mem.readIntLittle(u32, nonce[0..4]);140 c[1] = mem.readIntLittle(u32, nonce[0..4]);
...@@ -132,32 +144,63 @@ pub const suites = struct {...@@ -132,32 +144,63 @@ pub const suites = struct {
132 var context = ChaCha20Stream.initContext(server_key, c);144 var context = ChaCha20Stream.initContext(server_key, c);
133 var idx: usize = 0;145 var idx: usize = 0;
134 var buf: [64]u8 = undefined;146 var buf: [64]u8 = undefined;
135 ChaCha20Stream.chacha20Xor(147
136 buffer[0..actually_read],148 if (tag_length.tag == 0x15) {
137 encrypted[0..actually_read],149 var encrypted: [2]u8 = undefined;
138 server_key,150 reader.readNoEof(&encrypted) catch |err| switch (err) {
139 &context,151 error.EndOfStream => return error.ServerMalformedResponse,
140 &idx,152 else => |e| return e,
141 &buf,
142 );
143 if (actually_read < len) {
144 state.* = .{
145 .in_record = .{
146 .left = len - actually_read,
147 .context = context,
148 .idx = idx,
149 .buf = buf,
150 },
151 };153 };
152 } else {154 var result: [2] u8 = undefined;
153 // @TODO Verify Poly1305.155 ChaCha20Stream.chacha20Xor(
156 &result,
157 &encrypted,
158 server_key,
159 &context,
160 &idx,
161 &buf,
162 );
154 reader.skipBytes(16, .{}) catch |err| switch (err) {163 reader.skipBytes(16, .{}) catch |err| switch (err) {
155 error.EndOfStream => return 0,164 error.EndOfStream => return error.ServerMalformedResponse,
156 else => |e| return e,165 else => |e| return e,
157 };166 };
158 server_seq.* += 1;167 server_seq.* += 1;
159 }168 // CloseNotify
160 return actually_read;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;
161 },204 },
162 .in_record => |*record_info| {205 .in_record => |*record_info| {
163 const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);206 const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);
...@@ -177,7 +220,7 @@ pub const suites = struct {...@@ -177,7 +220,7 @@ pub const suites = struct {
177 if (record_info.left == 0) {220 if (record_info.left == 0) {
178 // @TODO Verify Poly1305.221 // @TODO Verify Poly1305.
179 reader.skipBytes(16, .{}) catch |err| switch (err) {222 reader.skipBytes(16, .{}) catch |err| switch (err) {
180 error.EndOfStream => return 0,223 error.EndOfStream => return error.ServerMalformedResponse,
181 else => |e| return e,224 else => |e| return e,
182 };225 };
183 state.* = .none;226 state.* = .none;
...@@ -293,12 +336,24 @@ pub const suites = struct {...@@ -293,12 +336,24 @@ pub const suites = struct {
293 ) !usize {336 ) !usize {
294 switch (state.*) {337 switch (state.*) {
295 .none => {338 .none => {
296 const len = (record_length(0x17, reader) catch |err| switch (err) {339 const tag_length = record_tag_length(reader) catch |err| switch (err) {
297 error.EndOfStream => return 0,340 error.EndOfStream => return 0,
298 else => |e| return e,341 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
303 var iv: [12]u8 = undefined;358 var iv: [12]u8 = undefined;
304 iv[0..4].* = key_data.server_iv(@This()).*;359 iv[0..4].* = key_data.server_iv(@This()).*;
...@@ -307,10 +362,6 @@ pub const suites = struct {...@@ -307,10 +362,6 @@ pub const suites = struct {
307 else => |e| return e,362 else => |e| return e,
308 };363 };
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
314 const aes = Aes.initEnc(key_data.server_key(@This()).*);365 const aes = Aes.initEnc(key_data.server_key(@This()).*);
315366
316 var j: [16]u8 = undefined;367 var j: [16]u8 = undefined;
...@@ -320,34 +371,66 @@ pub const suites = struct {...@@ -320,34 +371,66 @@ pub const suites = struct {
320 var counterInt = mem.readInt(u128, &j, .Big);371 var counterInt = mem.readInt(u128, &j, .Big);
321 var idx: usize = 0;372 var idx: usize = 0;
322373
323 ctr(374 if (tag_length.tag == 0x15) {
324 @TypeOf(aes),375 var encrypted: [2]u8 = undefined;
325 aes,376 reader.readNoEof(&encrypted) catch |err| switch (err) {
326 buffer[0..actually_read],377 error.EndOfStream => return error.ServerMalformedResponse,
327 encrypted[0..actually_read],378 else => |e| return e,
328 &counterInt,
329 &idx,
330 .Big,
331 );
332
333 if (actually_read < len) {
334 state.* = .{
335 .in_record = .{
336 .left = len - actually_read,
337 .aes = aes,
338 .counterInt = counterInt,
339 .idx = idx,
340 },
341 };379 };
342 } else {380
343 // @TODO Verify the message381 var result: [2]u8 = undefined;
382 ctr(
383 @TypeOf(aes),
384 aes,
385 &result,
386 &encrypted,
387 &counterInt,
388 &idx,
389 .Big,
390 );
344 reader.skipBytes(16, .{}) catch |err| switch (err) {391 reader.skipBytes(16, .{}) catch |err| switch (err) {
345 error.EndOfStream => return 0,392 error.EndOfStream => return error.ServerMalformedResponse,
346 else => |e| return e,393 else => |e| return e,
347 };394 };
348 server_seq.* += 1;395 server_seq.* += 1;
349 }396 // CloseNotify
350 return actually_read;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;
351 },434 },
352 .in_record => |*record_info| {435 .in_record => |*record_info| {
353 const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);436 const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);
...@@ -368,7 +451,7 @@ pub const suites = struct {...@@ -368,7 +451,7 @@ pub const suites = struct {
368 if (record_info.left == 0) {451 if (record_info.left == 0) {
369 // @TODO Verify Poly1305.452 // @TODO Verify Poly1305.
370 reader.skipBytes(16, .{}) catch |err| switch (err) {453 reader.skipBytes(16, .{}) catch |err| switch (err) {
371 error.EndOfStream => return 0,454 error.EndOfStream => return error.ServerMalformedResponse,
372 else => |e| return e,455 else => |e| return e,
373 };456 };
374 state.* = .none;457 state.* = .none;
...@@ -394,7 +477,7 @@ fn key_field_width(comptime T: type, comptime field: anytype) ?usize {...@@ -394,7 +477,7 @@ fn key_field_width(comptime T: type, comptime field: anytype) ?usize {
394 return @typeInfo(field_info.field_type).Array.len;477 return @typeInfo(field_info.field_type).Array.len;
395}478}
396479
397pub fn key_data_size(comptime ciphersuites: []const type) usize {480pub fn key_data_size(comptime ciphersuites: anytype) usize {
398 var max: usize = 0;481 var max: usize = 0;
399 for (ciphersuites) |cs| {482 for (ciphersuites) |cs| {
400 const curr = (key_field_width(cs.Keys, .client_mac) orelse 0) +483 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 {...@@ -409,7 +492,7 @@ pub fn key_data_size(comptime ciphersuites: []const type) usize {
409 return max;492 return max;
410}493}
411494
412pub fn KeyData(comptime ciphersuites: []const type) type {495pub fn KeyData(comptime ciphersuites: anytype) type {
413 return struct {496 return struct {
414 data: [key_data_size(ciphersuites)]u8,497 data: [key_data_size(ciphersuites)]u8,
415498
...@@ -455,7 +538,7 @@ pub fn KeyData(comptime ciphersuites: []const type) type {...@@ -455,7 +538,7 @@ pub fn KeyData(comptime ciphersuites: []const type) type {
455}538}
456539
457pub fn key_expansion(540pub fn key_expansion(
458 comptime ciphersuites: []const type,541 comptime ciphersuites: anytype,
459 tag: u16,542 tag: u16,
460 context: anytype,543 context: anytype,
461 comptime next_32_bytes: anytype,544 comptime next_32_bytes: anytype,
...@@ -505,7 +588,7 @@ pub fn key_expansion(...@@ -505,7 +588,7 @@ pub fn key_expansion(
505 unreachable;588 unreachable;
506}589}
507590
508pub fn ClientState(comptime ciphersuites: []const type) type {591pub fn ClientState(comptime ciphersuites: anytype) type {
509 var fields: [ciphersuites.len]std.builtin.TypeInfo.UnionField = undefined;592 var fields: [ciphersuites.len]std.builtin.TypeInfo.UnionField = undefined;
510 for (ciphersuites) |cs, i| {593 for (ciphersuites) |cs, i| {
511 fields[i] = .{594 fields[i] = .{
...@@ -524,7 +607,7 @@ pub fn ClientState(comptime ciphersuites: []const type) type {...@@ -524,7 +607,7 @@ pub fn ClientState(comptime ciphersuites: []const type) type {
524 });607 });
525}608}
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) {
528 inline for (ciphersuites) |cs| {611 inline for (ciphersuites) |cs| {
529 if (cs.tag == tag) {612 if (cs.tag == tag) {
530 return @unionInit(ClientState(ciphersuites), cs.name, cs.default_state);613 return @unionInit(ClientState(ciphersuites), cs.name, cs.default_state);
libs/iguanatls/src/crypto.zig+48
...@@ -243,6 +243,54 @@ pub const ecc = struct {...@@ -243,6 +243,54 @@ pub const ecc = struct {
243 }243 }
244 };244 };
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
246 fn jacobian_len(comptime Curve: type) usize {294 fn jacobian_len(comptime Curve: type) usize {
247 return @divTrunc(Curve.order.len * 8 + 61, 31);295 return @divTrunc(Curve.order.len * 8 + 61, 31);
248 }296 }
libs/iguanatls/src/main.zig+490-146
...@@ -23,6 +23,26 @@ fn handshake_record_length(reader: anytype) !usize {...@@ -23,6 +23,26 @@ fn handshake_record_length(reader: anytype) !usize {
23 return try record_length(0x16, reader);23 return try record_length(0x16, reader);
24}24}
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
26pub fn record_length(t: u8, reader: anytype) !usize {46pub fn record_length(t: u8, reader: anytype) !usize {
27 try check_record_type(t, reader);47 try check_record_type(t, reader);
28 var record_header: [4]u8 = undefined;48 var record_header: [4]u8 = undefined;
...@@ -72,39 +92,43 @@ fn check_record_type(...@@ -72,39 +92,43 @@ fn check_record_type(
7292
73 const severity = try reader.readByte();93 const severity = try reader.readByte();
74 const err_num = try reader.readByte();94 const err_num = try reader.readByte();
75 return switch (err_num) {95 return alert_byte_to_error(err_num);
76 0 => error.AlertCloseNotify,
77 10 => error.AlertUnexpectedMessage,
78 20 => error.AlertBadRecordMAC,
79 21 => error.AlertDecryptionFailed,
80 22 => error.AlertRecordOverflow,
81 30 => error.AlertDecompressionFailure,
82 40 => error.AlertHandshakeFailure,
83 41 => error.AlertNoCertificate,
84 42 => error.AlertBadCertificate,
85 43 => error.AlertUnsupportedCertificate,
86 44 => error.AlertCertificateRevoked,
87 45 => error.AlertCertificateExpired,
88 46 => error.AlertCertificateUnknown,
89 47 => error.AlertIllegalParameter,
90 48 => error.AlertUnknownCA,
91 49 => error.AlertAccessDenied,
92 50 => error.AlertDecodeError,
93 51 => error.AlertDecryptError,
94 60 => error.AlertExportRestriction,
95 70 => error.AlertProtocolVersion,
96 71 => error.AlertInsufficientSecurity,
97 80 => error.AlertInternalError,
98 90 => error.AlertUserCanceled,
99 100 => error.AlertNoRenegotiation,
100 110 => error.AlertUnsupportedExtension,
101 else => error.ServerMalformedResponse,
102 };
103 }96 }
104 if (record_type != expected)97 if (record_type != expected)
105 return error.ServerMalformedResponse;98 return error.ServerMalformedResponse;
106}99}
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
108fn Sha256Reader(comptime Reader: anytype) type {132fn Sha256Reader(comptime Reader: anytype) type {
109 const State = struct {133 const State = struct {
110 sha256: *Sha256,134 sha256: *Sha256,
...@@ -284,8 +308,39 @@ fn check_cert_timestamp(time: i64, tag_byte: u8, length: usize, reader: anytype)...@@ -284,8 +308,39 @@ fn check_cert_timestamp(time: i64, tag_byte: u8, length: usize, reader: anytype)
284 return error.CertificateVerificationFailed;308 return error.CertificateVerificationFailed;
285}309}
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 {
288 state.list.items[state.list.items.len - 1].dn = state.fbs.buffer[state.fbs.pos .. state.fbs.pos + length];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);
289}344}
290345
291fn add_cert_public_key(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void {346fn 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...@@ -302,8 +357,9 @@ fn add_server_cert(state: *VerifierCaptureState, tag_byte: u8, length: usize, re
302 const is_ca = state.list.items.len != 0;357 const is_ca = state.list.items.len != 0;
303358
304 const encoded_length = asn1.der.encode_length(length).slice();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
305 const cert_bytes = try state.allocator.alloc(u8, length + 1 + encoded_length.len);362 const cert_bytes = try state.allocator.alloc(u8, length + 1 + encoded_length.len);
306 errdefer state.allocator.free(cert_bytes);
307 cert_bytes[0] = tag_byte;363 cert_bytes[0] = tag_byte;
308 mem.copy(u8, cert_bytes[1 .. 1 + encoded_length.len], encoded_length);364 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...@@ -312,11 +368,11 @@ fn add_server_cert(state: *VerifierCaptureState, tag_byte: u8, length: usize, re
312 .is_ca = is_ca,368 .is_ca = is_ca,
313 .bytes = cert_bytes,369 .bytes = cert_bytes,
314 .dn = undefined,370 .dn = undefined,
315 .public_key = undefined,371 .common_name = &[0]u8{},
372 .public_key = x509.PublicKey.empty,
316 .signature = asn1.BitString{ .data = &[0]u8{}, .bit_len = 0 },373 .signature = asn1.BitString{ .data = &[0]u8{}, .bit_len = 0 },
317 .signature_algorithm = undefined,374 .signature_algorithm = undefined,
318 };375 };
319 errdefer state.allocator.free(state.list.items[state.list.items.len - 1].signature.data);
320376
321 const schema = .{377 const schema = .{
322 .sequence,378 .sequence,
...@@ -474,7 +530,6 @@ fn verify_signature(...@@ -474,7 +530,6 @@ fn verify_signature(
474 try llmod(&curr_base, modulus);530 try llmod(&curr_base, modulus);
475 try curr_exponent.shiftRight(curr_exponent, 1);531 try curr_exponent.shiftRight(curr_exponent, 1);
476 }532 }
477 try llmod(&encrypted_signature, modulus);
478 }533 }
479 // EMSA-PKCS1-V1_5-ENCODE534 // EMSA-PKCS1-V1_5-ENCODE
480 if (encrypted_signature.limbs.len * @sizeOf(usize) < signature.data.len)535 if (encrypted_signature.limbs.len * @sizeOf(usize) < signature.data.len)
...@@ -553,6 +608,7 @@ const SignatureAlgorithm = enum {...@@ -553,6 +608,7 @@ const SignatureAlgorithm = enum {
553const ServerCertificate = struct {608const ServerCertificate = struct {
554 bytes: []const u8,609 bytes: []const u8,
555 dn: []const u8,610 dn: []const u8,
611 common_name: []const u8,
556 public_key: x509.PublicKey,612 public_key: x509.PublicKey,
557 signature: asn1.BitString,613 signature: asn1.BitString,
558 signature_algorithm: SignatureAlgorithm,614 signature_algorithm: SignatureAlgorithm,
...@@ -566,8 +622,36 @@ const VerifierCaptureState = struct {...@@ -566,8 +622,36 @@ const VerifierCaptureState = struct {
566 fbs: *std.io.FixedBufferStream([]const u8),622 fbs: *std.io.FixedBufferStream([]const u8),
567};623};
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
569pub fn default_cert_verifier(653pub fn default_cert_verifier(
570 allocator: *std.mem.Allocator,654 allocator: *mem.Allocator,
571 reader: anytype,655 reader: anytype,
572 certs_bytes: usize,656 certs_bytes: usize,
573 trusted_certificates: []const x509.TrustAnchor,657 trusted_certificates: []const x509.TrustAnchor,
...@@ -622,6 +706,31 @@ pub fn default_cert_verifier(...@@ -622,6 +706,31 @@ pub fn default_cert_verifier(
622 return error.CertificateVerificationFailed;706 return error.CertificateVerificationFailed;
623707
624 const chain = capture_state.list.items;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
625 var i: usize = 0;734 var i: usize = 0;
626 while (i < chain.len - 1) : (i += 1) {735 while (i < chain.len - 1) : (i += 1) {
627 if (!try certificate_verify_signature(736 if (!try certificate_verify_signature(
...@@ -642,12 +751,7 @@ pub fn default_cert_verifier(...@@ -642,12 +751,7 @@ pub fn default_cert_verifier(
642 cert.public_key.eql(trusted.public_key))751 cert.public_key.eql(trusted.public_key))
643 {752 {
644 const key = chain[0].public_key;753 const key = chain[0].public_key;
645 chain[0].public_key = x509.PublicKey{754 chain[0].public_key = x509.PublicKey.empty;
646 .ec = .{
647 .id = undefined,
648 .curve_point = &[0]u8{},
649 },
650 };
651 return key;755 return key;
652 }756 }
653757
...@@ -662,12 +766,7 @@ pub fn default_cert_verifier(...@@ -662,12 +766,7 @@ pub fn default_cert_verifier(
662 trusted.public_key,766 trusted.public_key,
663 )) {767 )) {
664 const key = chain[0].public_key;768 const key = chain[0].public_key;
665 chain[0].public_key = x509.PublicKey{769 chain[0].public_key = x509.PublicKey.empty;
666 .ec = .{
667 .id = undefined,
668 .curve_point = &[0]u8{},
669 },
670 };
671 return key;770 return key;
672 }771 }
673 }772 }
...@@ -736,6 +835,155 @@ pub fn extract_cert_public_key(allocator: *Allocator, reader: anytype, length: u...@@ -736,6 +835,155 @@ pub fn extract_cert_public_key(allocator: *Allocator, reader: anytype, length: u
736 return capture_state.pub_key;835 return capture_state.pub_key;
737}836}
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
739pub fn client_connect(987pub fn client_connect(
740 options: anytype,988 options: anytype,
741 hostname: []const u8,989 hostname: []const u8,
...@@ -746,7 +994,10 @@ pub fn client_connect(...@@ -746,7 +994,10 @@ pub fn client_connect(
746)!Client(994)!Client(
747 @TypeOf(options.reader),995 @TypeOf(options.reader),
748 @TypeOf(options.writer),996 @TypeOf(options.writer),
749 options.ciphersuites,997 if (@hasField(@TypeOf(options), "ciphersuites"))
998 options.ciphersuites
999 else
1000 ciphersuites.all,
750 @hasField(@TypeOf(options), "protocols"),1001 @hasField(@TypeOf(options), "protocols"),
751) {1002) {
752 const Options = @TypeOf(options);1003 const Options = @TypeOf(options);
...@@ -761,6 +1012,20 @@ pub fn client_connect(...@@ -761,6 +1012,20 @@ pub fn client_connect(
761 @compileError("Option tuple is missing field 'trusted_certificates' for .default cert_verifier");1012 @compileError("Option tuple is missing field 'trusted_certificates' for .default cert_verifier");
762 }1013 }
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
764 const has_alpn = comptime @hasField(Options, "protocols");1029 const has_alpn = comptime @hasField(Options, "protocols");
765 var handshake_record_hash = Sha256.init(.{});1030 var handshake_record_hash = Sha256.init(.{});
766 const reader = options.reader;1031 const reader = options.reader;
...@@ -777,11 +1042,7 @@ pub fn client_connect(...@@ -777,11 +1042,7 @@ pub fn client_connect(
777 rand.bytes(&client_random);1042 rand.bytes(&client_random);
7781043
779 var server_random: [32]u8 = undefined;1044 var server_random: [32]u8 = undefined;
7801045 const ciphersuite_bytes = 2 * suites.len + 2;
781 if (options.ciphersuites.len == 0)
782 @compileError("Must provide at least one ciphersuite.");
783 const ciphersuite_bytes = 2 * options.ciphersuites.len + 2;
784 // @TODO Make sure the individual lengths are u16s
785 const alpn_bytes = if (has_alpn) blk: {1046 const alpn_bytes = if (has_alpn) blk: {
786 var sum: usize = 0;1047 var sum: usize = 0;
787 for (options.protocols) |proto| {1048 for (options.protocols) |proto| {
...@@ -789,6 +1050,7 @@ pub fn client_connect(...@@ -789,6 +1050,7 @@ pub fn client_connect(
789 }1050 }
790 break :blk 6 + options.protocols.len + sum;1051 break :blk 6 + options.protocols.len + sum;
791 } else 0;1052 } else 0;
1053 const curvelist_bytes = 2 * curvelist.len;
792 var protocol: if (has_alpn) []const u8 else void = undefined;1054 var protocol: if (has_alpn) []const u8 else void = undefined;
793 {1055 {
794 const client_hello_start = comptime blk: {1056 const client_hello_start = comptime blk: {
...@@ -810,7 +1072,7 @@ pub fn client_connect(...@@ -810,7 +1072,7 @@ pub fn client_connect(
810 // Same as above, couldnt achieve this with a single buffer.1072 // Same as above, couldnt achieve this with a single buffer.
811 // TLS_EMPTY_RENEGOTIATION_INFO_SCSV1073 // TLS_EMPTY_RENEGOTIATION_INFO_SCSV
812 var ciphersuite_buf: []const u8 = &[2]u8{ 0x00, 0x0f };1074 var ciphersuite_buf: []const u8 = &[2]u8{ 0x00, 0x0f };
813 for (options.ciphersuites) |cs, i| {1075 for (suites) |cs, i| {
814 // Also check for properties of the ciphersuites here1076 // Also check for properties of the ciphersuites here
815 if (cs.key_exchange != .ecdhe)1077 if (cs.key_exchange != .ecdhe)
816 @compileError("Non ECDHE key exchange is not supported yet.");1078 @compileError("Non ECDHE key exchange is not supported yet.");
...@@ -838,10 +1100,10 @@ pub fn client_connect(...@@ -838,10 +1100,10 @@ pub fn client_connect(
838 };1100 };
8391101
840 var msg_buf = client_hello_start.ptr[0..client_hello_start.len].*;1102 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));1103 mem.writeIntBig(u16, msg_buf[3..5], @intCast(u16, alpn_bytes + hostname.len + 0x55 + ciphersuite_bytes + curvelist_bytes));
842 mem.writeIntBig(u24, msg_buf[6..9], @intCast(u24, alpn_bytes + hostname.len + 0x55 + ciphersuite_bytes));1104 mem.writeIntBig(u24, msg_buf[6..9], @intCast(u24, alpn_bytes + hostname.len + 0x51 + ciphersuite_bytes + curvelist_bytes));
843 mem.copy(u8, msg_buf[11..43], &client_random);1105 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));
845 mem.writeIntBig(u16, msg_buf[52 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 5));1107 mem.writeIntBig(u16, msg_buf[52 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 5));
846 mem.writeIntBig(u16, msg_buf[54 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 3));1108 mem.writeIntBig(u16, msg_buf[54 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len + 3));
847 mem.writeIntBig(u16, msg_buf[57 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len));1109 mem.writeIntBig(u16, msg_buf[57 + ciphersuite_bytes ..][0..2], @intCast(u16, hostname.len));
...@@ -849,7 +1111,6 @@ pub fn client_connect(...@@ -849,7 +1111,6 @@ pub fn client_connect(
849 try hashing_writer.writeAll(msg_buf[5..]);1111 try hashing_writer.writeAll(msg_buf[5..]);
850 }1112 }
851 try hashing_writer.writeAll(hostname);1113 try hashing_writer.writeAll(hostname);
852 // @TODO Fix this with wikipedia test, add secp384r1 support (then app options.curves but default to all when not there (also do this for ciphersuites))
853 if (has_alpn) {1114 if (has_alpn) {
854 var msg_buf = [6]u8{ 0x00, 0x10, undefined, undefined, undefined, undefined };1115 var msg_buf = [6]u8{ 0x00, 0x10, undefined, undefined, undefined, undefined };
855 mem.writeIntBig(u16, msg_buf[2..4], @intCast(u16, alpn_bytes - 4));1116 mem.writeIntBig(u16, msg_buf[2..4], @intCast(u16, alpn_bytes - 4));
...@@ -860,20 +1121,37 @@ pub fn client_connect(...@@ -860,20 +1121,37 @@ pub fn client_connect(
860 try hashing_writer.writeAll(proto);1121 try hashing_writer.writeAll(proto);
861 }1122 }
862 }1123 }
863 try hashing_writer.writeAll(&[35]u8{1124
864 // Extension: supported groups, for now just x25519 (00 1D) and secp384r1 (00 0x18)1125 // Extension: supported groups
865 0x00, 0x0A, 0x00, 0x06, 0x00, 0x04, 0x00, 0x1D, 0x00, 0x18,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{
866 // Extension: EC point formats => uncompressed point format1143 // Extension: EC point formats => uncompressed point format
867 0x00, 0x0B, 0x00, 0x02, 0x01, 0x00,1144 0x00, 0x0B, 0x00, 0x02, 0x01, 0x00,
868 // Extension: Signature algorithms1145 // Extension: Signature algorithms
869 // RSA/PKCS1/SHA256, RSA/PKCS1/SHA5121146 // RSA/PKCS1/SHA256, RSA/PKCS1/SHA512
870 0x00, 0x0D, 0x00, 0x06,1147 0x00, 0x0D, 0x00, 0x06, 0x00, 0x04,
871 0x00, 0x04, 0x04, 0x01, 0x06, 0x01,1148 0x04, 0x01, 0x06, 0x01,
872 // Extension: Renegotiation Info => new connection1149 // Extension: Renegotiation Info => new connection
873 0xFF, 0x01, 0x00, 0x01,1150 0xFF, 0x01,
874 0x00,1151 0x00, 0x01, 0x00,
875 // Extension: SCT (signed certificate timestamp)1152 // Extension: SCT (signed certificate timestamp)
876 0x00, 0x12, 0x00, 0x00,1153 0x00, 0x12, 0x00,
1154 0x00,
877 });1155 });
8781156
879 // Read server hello1157 // Read server hello
...@@ -900,7 +1178,7 @@ pub fn client_connect(...@@ -900,7 +1178,7 @@ pub fn client_connect(
900 {1178 {
901 ciphersuite = try hashing_reader.readIntBig(u16);1179 ciphersuite = try hashing_reader.readIntBig(u16);
902 var found = false;1180 var found = false;
903 inline for (options.ciphersuites) |cs| {1181 inline for (suites) |cs| {
904 if (ciphersuite == cs.tag) {1182 if (ciphersuite == cs.tag) {
905 found = true;1183 found = true;
906 // TODO This segfaults stage11184 // TODO This segfaults stage1
...@@ -1002,8 +1280,10 @@ pub fn client_connect(...@@ -1002,8 +1280,10 @@ pub fn client_connect(
1002 }1280 }
1003 errdefer certificate_public_key.deinit(options.temp_allocator);1281 errdefer certificate_public_key.deinit(options.temp_allocator);
1004 // Read server ephemeral public key1282 // Read server ephemeral public key
1005 var server_public_key_buf: [97]u8 = undefined;1283 var server_public_key_buf: [curves.max_pub_key_len(curvelist)]u8 = undefined;
1006 var curve_id: enum { x25519, secp384r1 } = undefined;1284 var curve_id: u16 = undefined;
1285 var curve_id_buf: [3]u8 = undefined;
1286 var pub_key_len: u8 = undefined;
1007 {1287 {
1008 const length = try handshake_record_length(reader);1288 const length = try handshake_record_length(reader);
1009 {1289 {
...@@ -1012,24 +1292,38 @@ pub fn client_connect(...@@ -1012,24 +1292,38 @@ pub fn client_connect(
1012 if (handshake_header[0] != 0x0c)1292 if (handshake_header[0] != 0x0c)
1013 return error.ServerMalformedResponse;1293 return error.ServerMalformedResponse;
10141294
1015 // Only x25519 and secp384r1 supported for now.1295 try hashing_reader.readNoEof(&curve_id_buf);
1016 var curve_bytes: [3]u8 = undefined;1296 if (curve_id_buf[0] != 0x03)
1017 try hashing_reader.readNoEof(&curve_bytes);1297 return error.ServerMalformedResponse;
1018 curve_id = if (mem.eql(u8, &curve_bytes, "\x03\x00\x1D"))1298
1019 .x255191299 curve_id = mem.readIntBig(u16, curve_id_buf[1..]);
1020 else if (mem.eql(u8, &curve_bytes, "\x03\x00\x18"))1300 var found = false;
1021 .secp384r11301 inline for (curvelist) |curve| {
1022 else1302 if (curve.tag == curve_id) {
1303 found = true;
1304 // @TODO This break segfaults stage1
1305 // break;
1306 }
1307 }
1308 if (!found)
1023 return error.ServerInvalidCurve;1309 return error.ServerInvalidCurve;
1024 }1310 }
10251311
1026 const pub_key_len = try hashing_reader.readByte();1312 pub_key_len = try hashing_reader.readByte();
1027 if ((curve_id == .x25519 and pub_key_len != 32) or1313 inline for (curvelist) |curve| {
1028 (curve_id == .secp384r1 and pub_key_len != 97))1314 if (curve.tag == curve_id) {
1029 return error.ServerMalformedResponse;1315 if (curve.pub_key_len != pub_key_len)
1316 return error.ServerMalformedResponse;
1317 // @TODO This break segfaults stage1
1318 // break;
1319 }
1320 }
1321
1030 try hashing_reader.readNoEof(server_public_key_buf[0..pub_key_len]);1322 try hashing_reader.readNoEof(server_public_key_buf[0..pub_key_len]);
1031 if (curve_id == .secp384r1 and server_public_key_buf[0] != 0x04)1323 if (curve_id != curves.x25519.tag) {
1032 return error.ServerMalformedResponse;1324 if (server_public_key_buf[0] != 0x04)
1325 return error.ServerMalformedResponse;
1326 }
10331327
1034 // Signed public key1328 // Signed public key
1035 const signature_id = try hashing_reader.readIntBig(u16);1329 const signature_id = try hashing_reader.readIntBig(u16);
...@@ -1043,10 +1337,9 @@ pub fn client_connect(...@@ -1043,10 +1337,9 @@ pub fn client_connect(
1043 var sha256 = Sha256.init(.{});1337 var sha256 = Sha256.init(.{});
1044 sha256.update(&client_random);1338 sha256.update(&client_random);
1045 sha256.update(&server_random);1339 sha256.update(&server_random);
1046 switch (curve_id) {1340 sha256.update(&curve_id_buf);
1047 .x25519 => sha256.update("\x03\x00\x1D\x20"),1341 // @TODO Should this always be \x20 instead?
1048 .secp384r1 => sha256.update("\x03\x00\x18\x20"),1342 sha256.update(&[1]u8{pub_key_len});
1049 }
1050 sha256.update(server_public_key_buf[0..pub_key_len]);1343 sha256.update(server_public_key_buf[0..pub_key_len]);
1051 sha256.final(hash_buf[0..32]);1344 sha256.final(hash_buf[0..32]);
1052 hash = hash_buf[0..32];1345 hash = hash_buf[0..32];
...@@ -1057,10 +1350,7 @@ pub fn client_connect(...@@ -1057,10 +1350,7 @@ pub fn client_connect(
1057 var sha512 = Sha512.init(.{});1350 var sha512 = Sha512.init(.{});
1058 sha512.update(&client_random);1351 sha512.update(&client_random);
1059 sha512.update(&server_random);1352 sha512.update(&server_random);
1060 switch (curve_id) {1353 sha512.update(&curve_id_buf);
1061 .x25519 => sha512.update("\x03\x00\x1D"),
1062 .secp384r1 => sha512.update("\x03\x00\x18"),
1063 }
1064 sha512.update(&[1]u8{pub_key_len});1354 sha512.update(&[1]u8{pub_key_len});
1065 sha512.update(server_public_key_buf[0..pub_key_len]);1355 sha512.update(server_public_key_buf[0..pub_key_len]);
1066 sha512.final(hash_buf[0..64]);1356 sha512.final(hash_buf[0..64]);
...@@ -1083,7 +1373,7 @@ pub fn client_connect(...@@ -1083,7 +1373,7 @@ pub fn client_connect(
1083 return error.ServerInvalidSignature;1373 return error.ServerInvalidSignature;
10841374
1085 certificate_public_key.deinit(options.temp_allocator);1375 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;
1087 }1377 }
1088 // Read server hello done1378 // Read server hello done
1089 {1379 {
...@@ -1094,60 +1384,38 @@ pub fn client_connect(...@@ -1094,60 +1384,38 @@ pub fn client_connect(
1094 }1384 }
10951385
1096 // Generate keys for the session1386 // Generate keys for the session
1097 const client_key_pair: extern union {1387 const client_key_pair = curves.make_key_pair(curvelist, curve_id, rand);
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 };
11131388
1114 // Client key exchange1389 // Client key exchange
1115 switch (curve_id) {1390 try writer.writeAll(&[3]u8{ 0x16, 0x03, 0x03 });
1116 .x25519 => {1391 try writer.writeIntBig(u16, pub_key_len + 5);
1117 try writer.writeAll(&[5]u8{ 0x16, 0x03, 0x03, 0x00, 0x25 });1392 try hashing_writer.writeAll(&[5]u8{ 0x10, 0x00, 0x00, pub_key_len + 1, pub_key_len });
1118 try hashing_writer.writeAll(&[5]u8{ 0x10, 0x00, 0x00, 0x21, 0x20 });1393
1119 try hashing_writer.writeAll(&client_key_pair.x25519.public_key);1394 inline for (curvelist) |curve| {
1120 },1395 if (curve.tag == curve_id) {
1121 .secp384r1 => {1396 const actual_len = @typeInfo(std.meta.fieldInfo(curve.Keys, .public_key).field_type).Array.len;
1122 try writer.writeAll(&[5]u8{ 0x16, 0x03, 0x03, 0x00, 0x66 });1397 if (pub_key_len == actual_len + 1) {
1123 try hashing_writer.writeAll(&[6]u8{ 0x10, 0x00, 0x00, 0x62, 0x61, 0x04 });1398 try hashing_writer.writeByte(0x04);
1124 try hashing_writer.writeAll(&client_key_pair.secp384r1.public_key);1399 } else {
1125 },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 }
1126 }1405 }
1406
1127 // Client encryption keys calculation for ECDHE_RSA cipher suites with SHA256 hash1407 // Client encryption keys calculation for ECDHE_RSA cipher suites with SHA256 hash
1128 var master_secret: [48]u8 = undefined;1408 var master_secret: [48]u8 = undefined;
1129 var key_data: ciphers.KeyData(options.ciphersuites) = undefined;1409 var key_data: ciphers.KeyData(suites) = undefined;
1130 {1410 {
1131 var pre_master_secret_buf: [96]u8 = undefined;1411 var pre_master_secret_buf: [curves.max_pre_master_secret_len(curvelist)]u8 = undefined;
1132 const pre_master_secret = switch (curve_id) {1412 const pre_master_secret = try curves.make_pre_master_secret(
1133 .x25519 => blk: {1413 curvelist,
1134 pre_master_secret_buf[0..32].* = std.crypto.dh.X25519.scalarmult(1414 curve_id,
1135 client_key_pair.x25519.secret_key,1415 client_key_pair,
1136 server_public_key_buf[0..32].*,1416 &pre_master_secret_buf,
1137 ) catch1417 server_public_key_buf,
1138 return error.PreMasterGenerationFailed;1418 );
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 };
11511419
1152 var seed: [77]u8 = undefined;1420 var seed: [77]u8 = undefined;
1153 seed[0..13].* = "master secret".*;1421 seed[0..13].* = "master secret".*;
...@@ -1209,7 +1477,7 @@ pub fn client_connect(...@@ -1209,7 +1477,7 @@ pub fn client_connect(
1209 .master_secret = &master_secret,1477 .master_secret = &master_secret,
1210 };1478 };
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);
1213 }1481 }
12141482
1215 // Client change cipher spec and client handshake finished1483 // Client change cipher spec and client handshake finished
...@@ -1245,7 +1513,7 @@ pub fn client_connect(...@@ -1245,7 +1513,7 @@ pub fn client_connect(
1245 }1513 }
1246 handshake_record_hash.update(&verify_message);1514 handshake_record_hash.update(&verify_message);
12471515
1248 inline for (options.ciphersuites) |cs| {1516 inline for (suites) |cs| {
1249 if (cs.tag == ciphersuite) {1517 if (cs.tag == ciphersuite) {
1250 try cs.raw_write(1518 try cs.raw_write(
1251 256,1519 256,
...@@ -1285,7 +1553,7 @@ pub fn client_connect(...@@ -1285,7 +1553,7 @@ pub fn client_connect(
1285 verify_message[4..16].* = p1[0..12].*;1553 verify_message[4..16].* = p1[0..12].*;
1286 }1554 }
12871555
1288 inline for (options.ciphersuites) |cs| {1556 inline for (suites) |cs| {
1289 if (cs.tag == ciphersuite) {1557 if (cs.tag == ciphersuite) {
1290 if (!try cs.check_verify_message(&key_data, length, reader, verify_message))1558 if (!try cs.check_verify_message(&key_data, length, reader, verify_message))
1291 return error.ServerInvalidVerifyData;1559 return error.ServerInvalidVerifyData;
...@@ -1293,10 +1561,10 @@ pub fn client_connect(...@@ -1293,10 +1561,10 @@ pub fn client_connect(
1293 }1561 }
1294 }1562 }
12951563
1296 return Client(@TypeOf(reader), @TypeOf(writer), options.ciphersuites, has_alpn){1564 return Client(@TypeOf(reader), @TypeOf(writer), suites, has_alpn){
1297 .ciphersuite = ciphersuite,1565 .ciphersuite = ciphersuite,
1298 .key_data = key_data,1566 .key_data = key_data,
1299 .state = ciphers.client_state_default(options.ciphersuites, ciphersuite),1567 .state = ciphers.client_state_default(suites, ciphersuite),
1300 .rand = rand,1568 .rand = rand,
1301 .parent_reader = reader,1569 .parent_reader = reader,
1302 .parent_writer = writer,1570 .parent_writer = writer,
...@@ -1418,8 +1686,9 @@ test "HTTPS request on wikipedia main page" {...@@ -1418,8 +1686,9 @@ test "HTTPS request on wikipedia main page" {
1418 .cert_verifier = .default,1686 .cert_verifier = .default,
1419 .temp_allocator = std.testing.allocator,1687 .temp_allocator = std.testing.allocator,
1420 .trusted_certificates = trusted_chain.data.items,1688 .trusted_certificates = trusted_chain.data.items,
1421 .ciphersuites = ciphersuites.all,1689 .ciphersuites = .{ciphersuites.ECDHE_RSA_Chacha20_Poly1305},
1422 .protocols = &[_][]const u8{"http/1.1"},1690 .protocols = &[_][]const u8{"http/1.1"},
1691 .curves = .{curves.x25519},
1423 }, "en.wikipedia.org");1692 }, "en.wikipedia.org");
1424 defer client.close_notify() catch {};1693 defer client.close_notify() catch {};
14251694
...@@ -1471,9 +1740,9 @@ test "HTTPS request on twitch oath2 endpoint" {...@@ -1471,9 +1740,9 @@ test "HTTPS request on twitch oath2 endpoint" {
1471 .reader = sock.reader(),1740 .reader = sock.reader(),
1472 .writer = sock.writer(),1741 .writer = sock.writer(),
1473 .cert_verifier = .none,1742 .cert_verifier = .none,
1474 .ciphersuites = ciphersuites.all,
1475 .protocols = &[_][]const u8{"http/1.1"},1743 .protocols = &[_][]const u8{"http/1.1"},
1476 }, "id.twitch.tv");1744 }, "id.twitch.tv");
1745 std.testing.expectEqualStrings("http/1.1", client.protocol);
1477 defer client.close_notify() catch {};1746 defer client.close_notify() catch {};
14781747
1479 try client.writer().writeAll("GET /oauth2/validate HTTP/1.1\r\nHost: id.twitch.tv\r\nAccept: */*\r\n\r\n");1748 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" {...@@ -1497,3 +1766,78 @@ test "HTTPS request on twitch oath2 endpoint" {
14971766
1498 try client.reader().readNoEof(html_contents);1767 try client.reader().readNoEof(html_contents);
1499}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+196-206
...@@ -21,6 +21,8 @@ pub const CurveId = enum {...@@ -21,6 +21,8 @@ pub const CurveId = enum {
21// zig fmt: on21// zig fmt: on
2222
23pub const PublicKey = union(enum) {23pub const PublicKey = union(enum) {
24 pub const empty = PublicKey{ .ec = .{ .id = undefined, .curve_point = &[0]u8{} } };
25
24 /// RSA public key26 /// RSA public key
25 rsa: struct {27 rsa: struct {
26 //Positive std.math.big.int.Const numbers.28 //Positive std.math.big.int.Const numbers.
...@@ -45,7 +47,7 @@ pub const PublicKey = union(enum) {...@@ -45,7 +47,7 @@ pub const PublicKey = union(enum) {
45 }47 }
4648
47 pub fn eql(self: @This(), other: @This()) bool {49 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))
49 return false;51 return false;
50 switch (self) {52 switch (self) {
51 .rsa => |mod_exp| return mem.eql(usize, mod_exp.exponent, other.rsa.exponent) and53 .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 {...@@ -63,7 +65,7 @@ pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey {
63 if ((try reader.readByte()) != 0x06)65 if ((try reader.readByte()) != 0x06)
64 return error.MalformedDER;66 return error.MalformedDER;
65 const oid_bytes = try asn1.der.parse_length(reader);67 const oid_bytes = try asn1.der.parse_length(reader);
66 if (oid_bytes == 9 ) {68 if (oid_bytes == 9) {
67 // @TODO This fails in async if merged with the if69 // @TODO This fails in async if merged with the if
68 if (!try reader.isBytes(&[9]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1 }))70 if (!try reader.isBytes(&[9]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1 }))
69 return error.MalformedDER;71 return error.MalformedDER;
...@@ -127,16 +129,15 @@ pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey {...@@ -127,16 +129,15 @@ pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey {
127 // 1.3.132.0.{34, 35}129 // 1.3.132.0.{34, 35}
128 const last_byte = try reader.readByte();130 const last_byte = try reader.readByte();
129 if (last_byte == 0x22)131 if (last_byte == 0x22)
130 key.ec = .{ .id = .secp384r1, .curve_point = undefined }132 key = .{ .ec = .{ .id = .secp384r1, .curve_point = undefined } }
131 else if (last_byte == 0x23)133 else if (last_byte == 0x23)
132 key.ec = .{ .id = .secp521r1, .curve_point = undefined }134 key = .{ .ec = .{ .id = .secp521r1, .curve_point = undefined } }
133 else135 else
134 return error.MalformedDER;136 return error.MalformedDER;
135 } else if (curve_oid_bytes == 8)137 } else if (curve_oid_bytes == 8) {
136 {
137 if (!try reader.isBytes(&[8]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x3, 0x1, 0x7 }))138 if (!try reader.isBytes(&[8]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x3, 0x1, 0x7 }))
138 return error.MalformedDER;139 return error.MalformedDER;
139 key.ec = .{ .id = .secp256r1, .curve_point = undefined };140 key = .{ .ec = .{ .id = .secp256r1, .curve_point = undefined } };
140 } else {141 } else {
141 return error.MalformedDER;142 return error.MalformedDER;
142 }143 }
...@@ -150,7 +151,7 @@ pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey {...@@ -150,7 +151,7 @@ pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey {
150 return error.MalformedDER;151 return error.MalformedDER;
151 const bit_memory = try allocator.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable);152 const bit_memory = try allocator.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable);
152 errdefer allocator.free(bit_memory);153 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
155 key.ec.curve_point = bit_memory;156 key.ec.curve_point = bit_memory;
156 return key;157 return key;
...@@ -199,14 +200,21 @@ pub const TrustAnchor = struct {...@@ -199,14 +200,21 @@ pub const TrustAnchor = struct {
199200
200 const data = object_id.object_identifier.data;201 const data = object_id.object_identifier.data;
201 // Basic constraints extension202 // 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)
203 return;204 return;
204205
205 const basic_constraints = try asn1.der.parse_value(state.allocator, reader);206 const basic_constraints = try asn1.der.parse_value(state.allocator, reader);
206 defer basic_constraints.deinit(state.allocator);207 defer basic_constraints.deinit(state.allocator);
207 if (basic_constraints != .bool)208
208 return error.DoesNotMatchSchema;209 switch (basic_constraints) {
209 state.self.is_ca = basic_constraints.bool;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 }
210 }218 }
211219
212 fn initExtensions(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void {220 fn initExtensions(state: *CaptureState, tag_byte: u8, length: usize, reader: anytype) !void {
...@@ -344,17 +352,7 @@ pub const TrustAnchorChain = struct {...@@ -344,17 +352,7 @@ pub const TrustAnchorChain = struct {
344 var buffered = std.io.bufferedReader(cert_reader);352 var buffered = std.io.bufferedReader(cert_reader);
345 const anchor = try TrustAnchor.create(allocator, buffered.reader());353 const anchor = try TrustAnchor.create(allocator, buffered.reader());
346 errdefer anchor.deinit(allocator);354 errdefer anchor.deinit(allocator);
347355 try self.data.append(anchor);
348 // This read forces the cert reader to find the `-----END`
349 // @TODO Should work without this read, investigate
350 _ = cert_reader.readByte() catch |err| switch (err) {
351 error.EndOfStream => {
352 try self.data.append(anchor);
353 break;
354 },
355 else => |e| return e,
356 };
357 return error.MalformedDER;
358 }356 }
359 return self;357 return self;
360 }358 }
...@@ -367,75 +365,54 @@ pub const TrustAnchorChain = struct {...@@ -367,75 +365,54 @@ pub const TrustAnchorChain = struct {
367};365};
368366
369fn PEMSectionReader(comptime Reader: type) type {367fn PEMSectionReader(comptime Reader: type) type {
370 const ReadError = Reader.Error || error{MalformedPEM};368 const Error = Reader.Error || error{MalformedPEM};
371 const S = struct {369 const read = struct {
372 pub fn read(self: *PEMCertificateIterator(Reader), buffer: []u8) ReadError!usize {370 fn f(it: *PEMCertificateIterator(Reader), buf: []u8) Error!usize {
373 const end = "-----END ";
374 var end_letters_matched: ?usize = null;
375
376 var out_idx: usize = 0;371 var out_idx: usize = 0;
377 if (self.waiting_chars_len > 0) {372 if (it.waiting_chars_len > 0) {
378 const rest_written = std.math.min(self.waiting_chars_len, buffer.len);373 const rest_written = std.math.min(it.waiting_chars_len, buf.len);
379 while (out_idx < rest_written) : (out_idx += 1) {374 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];
381 }376 }
382377
383 self.waiting_chars_len -= rest_written;378 it.waiting_chars_len -= rest_written;
384 if (self.waiting_chars_len != 0) {379 if (it.waiting_chars_len != 0) {
385 std.mem.copy(u8, self.waiting_chars[0..], self.waiting_chars[rest_written..]);380 std.mem.copy(u8, it.waiting_chars[0..], it.waiting_chars[rest_written..]);
386 }381 }
387382
388 if (out_idx == buffer.len) {383 if (out_idx == buf.len) {
389 return out_idx;384 return out_idx;
390 }385 }
391 }386 }
387 if (it.state != .in_section)
388 return out_idx;
392389
393 var base64_buf: [4]u8 = undefined;390 var base64_buf: [4]u8 = undefined;
394 var base64_idx: usize = 0;391 var base64_idx: usize = 0;
395
396 while (true) {392 while (true) {
397 var byte = self.reader.readByte() catch |err| switch (err) {393 const byte = it.reader.readByte() catch |err| switch (err) {
398 error.EndOfStream => {394 error.EndOfStream => return out_idx,
399 if (self.skip_to_newline_exit) {
400 self.state = .none;
401 return 0;
402 }
403 return error.MalformedPEM;
404 },
405 else => |e| return e,395 else => |e| return e,
406 };396 };
407397
408 if (self.skip_to_newline_exit) {398 if (byte == '-') {
409 if (byte == '\n') {399 if (it.reader.isBytes("----END ") catch |err| switch (err) {
410 self.skip_to_newline_exit = false;400 error.EndOfStream => return error.MalformedPEM,
411 self.state = .none;401 else => |e| return e,
412 return 0;402 }) {
413 }403 try it.reader.skipUntilDelimiterOrEof('\n');
414 continue;404 it.state = .none;
415 }405 return out_idx;
416
417 if (byte == '\n' or byte == '\r') {
418 self.empty_line = true;
419 continue;
420 }
421 defer self.empty_line = false;
422
423 if (end_letters_matched) |*matched| {
424 if (end[matched.*] == byte) {
425 matched.* += 1;
426 if (matched.* == end.len) {
427 self.skip_to_newline_exit = true;
428 if (out_idx > 0)
429 return out_idx
430 else
431 continue;
432 }
433 continue;
434 } else return error.MalformedPEM;406 } else return error.MalformedPEM;
435 } else if (self.empty_line and end[0] == byte) {407 } else if (byte == '\r') {
436 end_letters_matched = 1;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')
437 continue;415 continue;
438 }
439416
440 base64_buf[base64_idx] = byte;417 base64_buf[base64_idx] = byte;
441 base64_idx += 1;418 base64_idx += 1;
...@@ -444,8 +421,9 @@ fn PEMSectionReader(comptime Reader: type) type {...@@ -444,8 +421,9 @@ fn PEMSectionReader(comptime Reader: type) type {
444421
445 const out_len = std.base64.standard_decoder.calcSize(&base64_buf) catch422 const out_len = std.base64.standard_decoder.calcSize(&base64_buf) catch
446 return error.MalformedPEM;423 return error.MalformedPEM;
447 const rest_chars = if (out_len > buffer.len - out_idx)424
448 out_len - (buffer.len - out_idx)425 const rest_chars = if (out_len > buf.len - out_idx)
426 out_len - (buf.len - out_idx)
449 else427 else
450 0;428 0;
451 const buf_chars = out_len - rest_chars;429 const buf_chars = out_len - rest_chars;
...@@ -455,21 +433,26 @@ fn PEMSectionReader(comptime Reader: type) type {...@@ -455,21 +433,26 @@ fn PEMSectionReader(comptime Reader: type) type {
455433
456 var i: u3 = 0;434 var i: u3 = 0;
457 while (i < buf_chars) : (i += 1) {435 while (i < buf_chars) : (i += 1) {
458 buffer[out_idx] = res_buffer[i];436 buf[out_idx] = res_buffer[i];
459 out_idx += 1;437 out_idx += 1;
460 }438 }
461439
462 if (rest_chars > 0) {440 if (rest_chars > 0) {
463 mem.copy(u8, &self.waiting_chars, res_buffer[i..]);441 mem.copy(u8, &it.waiting_chars, res_buffer[i..]);
464 self.waiting_chars_len = @intCast(u2, rest_chars);442 it.waiting_chars_len = @intCast(u2, rest_chars);
465 }443 }
466 if (out_idx == buffer.len)444 if (out_idx == buf.len)
467 return out_idx;445 return out_idx;
468 }446 }
469 }447 }
470 }448 }
471 };449 }.f;
472 return std.io.Reader(*PEMCertificateIterator(Reader), ReadError, S.read);450
451 return std.io.Reader(
452 *PEMCertificateIterator(Reader),
453 Error,
454 read,
455 );
473}456}
474457
475fn PEMCertificateIterator(comptime Reader: type) type {458fn PEMCertificateIterator(comptime Reader: type) type {
...@@ -479,141 +462,86 @@ fn PEMCertificateIterator(comptime Reader: type) type {...@@ -479,141 +462,86 @@ fn PEMCertificateIterator(comptime Reader: type) type {
479462
480 reader: Reader,463 reader: Reader,
481 // Internal state for the iterator and the current reader.464 // Internal state for the iterator and the current reader.
482 skip_to_newline_exit: bool = false,
483 empty_line: bool = false,
484 waiting_chars: [4]u8 = undefined,
485 waiting_chars_len: u2 = 0,
486 state: enum {465 state: enum {
487 none,466 none,
488 in_section_name,
489 in_cert,
490 in_other,467 in_other,
468 in_section,
491 } = .none,469 } = .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
493 pub fn next(self: *@This()) NextError!?SectionReader {474 pub fn next(self: *@This()) NextError!?SectionReader {
494 const end = "-----END ";475 self.waiting_chars_len = 0;
495 const begin = "-----BEGIN ";
496 const certificate = "CERTIFICATE";
497 const x509_certificate = "X.509 CERTIFICATE";
498
499 var line_empty = true;
500 var end_letters_matched: ?usize = null;
501 var begin_letters_matched: ?usize = null;
502 var certificate_letters_matched: ?usize = null;
503 var x509_certificate_letters_matched: ?usize = null;
504 var skip_to_newline = false;
505 var return_after_skip = false;
506
507 var base64_buf: [4]u8 = undefined;
508 var base64_buf_idx: usize = 0;
509
510 // Called next before reading all of the previous cert.
511 if (self.state == .in_cert) {
512 self.waiting_chars_len = 0;
513 }
514
515 while (true) {476 while (true) {
516 var last_byte = false;
517 const byte = self.reader.readByte() catch |err| switch (err) {477 const byte = self.reader.readByte() catch |err| switch (err) {
518 error.EndOfStream => blk: {478 error.EndOfStream => if (self.state == .none)
519 if (line_empty and self.state == .none) {479 return null
520 return null;480 else
521 } else {481 return error.EndOfStream,
522 last_byte = true;
523 break :blk '\n';
524 }
525 },
526 else => |e| return e,482 else => |e| return e,
527 };483 };
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
547 switch (self.state) {485 switch (self.state) {
548 .none => {486 .none => switch (byte) {
549 if (begin_letters_matched) |*matched| {487 '#' => {
550 if (begin[matched.*] != byte)488 try self.reader.skipUntilDelimiterOrEof('\n');
551 return error.MalformedPEM;489 continue;
552490 },
553 matched.* += 1;491 '\r', '\n', ' ', '\t' => continue,
554 if (matched.* == begin.len) {492 '-' => {
555 self.state = .in_section_name;493 if (try self.reader.isBytes("----BEGIN ")) {
556 line_empty = true;494 const first_section_byte = try self.reader.readByte();
557 begin_letters_matched = null;495 if (first_section_byte == 'C') {
558 }496 const rest = "ERTIFICATE";
559 } else if (begin[0] == byte) {497 var matched: usize = 0;
560 begin_letters_matched = 1;498 while ((try self.reader.readByte()) == rest[matched]) : (matched += 1) {
561 } else if (mem.indexOfScalar(u8, &std.ascii.spaces, byte) != null) {499 if (matched == rest.len - 1) {
562 if (last_byte) return null;500 try self.reader.skipUntilDelimiterOrEof('\n');
563 } else return error.MalformedPEM;501 self.state = .in_section;
564 },502 return SectionReader{ .context = self };
565 .in_section_name => {503 }
566 if (certificate_letters_matched) |*matched| {504 }
567 if (certificate[matched.*] != byte) {505 try self.reader.skipUntilDelimiterOrEof('\n');
568 self.state = .in_other;506 self.state = .in_other;
569 skip_to_newline = true;507 continue;
570 continue;508 } else if (first_section_byte == 'X') {
571 }509 const rest = ".509 CERTIFICATE";
572 matched.* += 1;510 var matched: usize = 0;
573 if (matched.* == certificate.len) {511 while ((try self.reader.readByte()) == rest[matched]) : (matched += 1) {
574 self.state = .in_cert;512 if (matched == rest.len - 1) {
575 certificate_letters_matched = null;513 try self.reader.skipUntilDelimiterOrEof('\n');
576 skip_to_newline = true;514 self.state = .in_section;
577 return_after_skip = true;515 return SectionReader{ .context = self };
578 }516 }
579 } else if (x509_certificate_letters_matched) |*matched| {517 }
580 if (x509_certificate[matched.*] != byte) {518 try self.reader.skipUntilDelimiterOrEof('\n');
581 self.state = .in_other;519 self.state = .in_other;
582 skip_to_newline = true;520 continue;
583 continue;521 } else {
584 }522 try self.reader.skipUntilDelimiterOrEof('\n');
585 matched.* += 1;523 self.state = .in_other;
586 if (matched.* == x509_certificate.len) {524 continue;
587 self.state = .in_cert;525 }
588 x509_certificate_letters_matched = null;526 } else return error.MalformedPEM;
589 skip_to_newline = true;527 },
590 return_after_skip = true;528 else => return error.MalformedPEM,
591 }
592 } else if (line_empty and certificate[0] == byte) {
593 certificate_letters_matched = 1;
594 } else if (line_empty and x509_certificate[0] == byte) {
595 x509_certificate_letters_matched = 1;
596 } else if (line_empty) {
597 self.state = .in_other;
598 skip_to_newline = true;
599 } else unreachable;
600 },529 },
601 .in_other, .in_cert => {530 .in_other, .in_section => switch (byte) {
602 if (end_letters_matched) |*matched| {531 '#' => {
603 if (end[matched.*] != byte) {532 try self.reader.skipUntilDelimiterOrEof('\n');
604 end_letters_matched = null;533 continue;
605 skip_to_newline = true;534 },
606 continue;535 '\r', '\n', ' ', '\t' => continue,
607 }536 '-' => {
608 matched.* += 1;537 if (try self.reader.isBytes("----END ")) {
609 if (matched.* == end.len) {538 try self.reader.skipUntilDelimiterOrEof('\n');
610 self.state = .none;539 self.state = .none;
611 end_letters_matched = null;540 continue;
612 skip_to_newline = true;541 } else return error.MalformedPEM;
613 }542 },
614 } else if (line_empty and end[0] == byte) {543 // @TODO Make sure the character is base64
615 end_letters_matched = 1;544 else => continue,
616 }
617 },545 },
618 }546 }
619 }547 }
...@@ -696,9 +624,8 @@ test "pemCertificateIterator" {...@@ -696,9 +624,8 @@ test "pemCertificateIterator" {
696624
697 // Try reading byte by byte from a cert reader625 // Try reading byte by byte from a cert reader
698 {626 {
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);
700 var it = pemCertificateIterator(fbs.reader());628 var it = pemCertificateIterator(fbs.reader());
701
702 // Read a couple of bytes from the first reader, then skip to the next629 // Read a couple of bytes from the first reader, then skip to the next
703 {630 {
704 const first_reader = (try it.next()) orelse return error.NoCertificate;631 const first_reader = (try it.next()) orelse return error.NoCertificate;
...@@ -725,7 +652,70 @@ test "pemCertificateIterator" {...@@ -725,7 +652,70 @@ test "pemCertificateIterator" {
725}652}
726653
727test "TrustAnchorChain" {654test "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 );
729 const chain = try TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader());719 const chain = try TrustAnchorChain.from_pem(std.testing.allocator, fbs.reader());
730 defer chain.deinit();720 defer chain.deinit();
731}721}
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-----