| author | |
| committer | |
| log | 194bee3eba0a55f928dd00fb4fa5d71ddc54aeb6 |
| tree | 038f8740f72d27f7496e7d648313a27ee69d2679 |
| parent | 1a6702c54e028a085d8553648e5f8e2604b29cc5 |
8 files changed, 1612 insertions(+), 418 deletions(-)
libs/iguanatls/bench/bench.zig created+425| ... | @@ -0,0 +1,425 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const tls = @import("tls"); | ||
| 3 | const use_gpa = @import("build_options").use_gpa; | ||
| 4 | |||
| 5 | pub const log_level = .debug; | ||
| 6 | |||
| 7 | const 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 | |||
| 74 | const 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 | |||
| 83 | const ReplayingReaderState = struct { | ||
| 84 | data: []const u8, | ||
| 85 | }; | ||
| 86 | const 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 | |||
| 96 | const 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 | |||
| 109 | fn 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 | |||
| 265 | fn 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 | |||
| 310 | pub 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 @@ | ||
| 1 | const Builder = @import("std").build.Builder; | ||
| 2 | |||
| 3 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const tls = @import("tls"); | ||
| 3 | |||
| 4 | const 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 | |||
| 18 | fn 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 | |||
| 33 | fn 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 | |||
| 41 | fn 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 | |||
| 116 | fn 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 | |||
| 157 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||
| 158 | pub 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; |
| 4 | usingnamespace @import("crypto.zig"); | 4 | usingnamespace @import("crypto.zig"); |
| 5 | const Chacha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305; | 5 | const Chacha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305; |
| 6 | const Aes128Gcm = std.crypto.aead.aes_gcm.Aes128Gcm; | 6 | const Aes128Gcm = std.crypto.aead.aes_gcm.Aes128Gcm; |
| 7 | const record_length = @import("main.zig").record_length; | 7 | |
| 8 | const main = @import("main.zig"); | ||
| 9 | const alert_byte_to_error = main.alert_byte_to_error; | ||
| 10 | const record_tag_length = main.record_tag_length; | ||
| 11 | const record_length = main.record_length; | ||
| 8 | 12 | ||
| 9 | pub const suites = struct { | 13 | pub 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 | } | ||
| 113 | 126 | ||
| 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); | ||
| 115 | 131 | ||
| 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 | } |
| 121 | 137 | ||
| 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 | } | ||
| 300 | 352 | ||
| 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); | ||
| 302 | 357 | ||
| 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 | }; |
| 309 | 364 | ||
| 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()).*); |
| 315 | 366 | ||
| 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; |
| 322 | 373 | ||
| 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 message | 381 | 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 | } |
| 396 | 479 | ||
| 397 | pub fn key_data_size(comptime ciphersuites: []const type) usize { | 480 | pub 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 | } |
| 411 | 494 | ||
| 412 | pub fn KeyData(comptime ciphersuites: []const type) type { | 495 | pub 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, |
| 415 | 498 | ||
| ... | @@ -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 | } |
| 456 | 539 | ||
| 457 | pub fn key_expansion( | 540 | pub 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 | } |
| 507 | 590 | ||
| 508 | pub fn ClientState(comptime ciphersuites: []const type) type { | 591 | pub 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 | } |
| 526 | 609 | ||
| 527 | pub fn client_state_default(comptime ciphersuites: []const type, tag: u16) ClientState(ciphersuites) { | 610 | pub 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 | }; |
| 245 | 245 | ||
| 246 | pub const SECP256R1 = struct { | ||
| 247 | pub const point_len = 64; | ||
| 248 | |||
| 249 | const order = [point_len / 2]u8{ | ||
| 250 | 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, | ||
| 251 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||
| 252 | 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, | ||
| 253 | 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, | ||
| 254 | }; | ||
| 255 | |||
| 256 | const P = [_]u32{ | ||
| 257 | 0x00000108, 0x7FFFFFFF, | ||
| 258 | 0x7FFFFFFF, 0x7FFFFFFF, | ||
| 259 | 0x00000007, 0x00000000, | ||
| 260 | 0x00000000, 0x00000040, | ||
| 261 | 0x7FFFFF80, 0x000000FF, | ||
| 262 | }; | ||
| 263 | const R2 = [_]u32{ | ||
| 264 | 0x00000108, 0x00014000, | ||
| 265 | 0x00018000, 0x00000000, | ||
| 266 | 0x7FF40000, 0x7FEFFFFF, | ||
| 267 | 0x7FF7FFFF, 0x7FAFFFFF, | ||
| 268 | 0x005FFFFF, 0x00000000, | ||
| 269 | }; | ||
| 270 | const B = [_]u32{ | ||
| 271 | 0x00000108, 0x6FEE1803, | ||
| 272 | 0x6229C4BD, 0x21B139BE, | ||
| 273 | 0x327150AA, 0x3567802E, | ||
| 274 | 0x3F7212ED, 0x012E4355, | ||
| 275 | 0x782DD38D, 0x0000000E, | ||
| 276 | }; | ||
| 277 | |||
| 278 | const base_point = [point_len]u8{ | ||
| 279 | 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, | ||
| 280 | 0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40, 0xF2, | ||
| 281 | 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, | ||
| 282 | 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96, | ||
| 283 | 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, | ||
| 284 | 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, | ||
| 285 | 0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE, | ||
| 286 | 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5, | ||
| 287 | }; | ||
| 288 | |||
| 289 | comptime { | ||
| 290 | std.debug.assert((P[0] - (P[0] >> 5) + 7) >> 2 == point_len + 1); | ||
| 291 | } | ||
| 292 | }; | ||
| 293 | |||
| 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 | } |
| 25 | 25 | ||
| 26 | pub const RecordTagLength = struct { | ||
| 27 | tag: u8, | ||
| 28 | length: u16, | ||
| 29 | }; | ||
| 30 | pub 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 | |||
| 26 | pub fn record_length(t: u8, reader: anytype) !usize { | 46 | pub 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( |
| 72 | 92 | ||
| 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 | } |
| 107 | 100 | ||
| 101 | pub 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 | |||
| 108 | fn Sha256Reader(comptime Reader: anytype) type { | 132 | fn 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 | } |
| 286 | 310 | ||
| 287 | fn add_cert_subject_dn(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void { | 311 | fn 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 | |||
| 332 | fn 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 | } |
| 290 | 345 | ||
| 291 | fn add_cert_public_key(state: *VerifierCaptureState, _: u8, length: usize, reader: anytype) !void { | 346 | fn 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; |
| 303 | 358 | ||
| 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); |
| 309 | 365 | ||
| ... | @@ -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); | ||
| 320 | 376 | ||
| 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-ENCODE | 534 | // 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 { |
| 553 | const ServerCertificate = struct { | 608 | const 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 | }; |
| 568 | 624 | ||
| 625 | // @TODO Move out of here | ||
| 626 | const 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 | |||
| 644 | fn 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 | |||
| 569 | pub fn default_cert_verifier( | 653 | pub 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; |
| 623 | 707 | ||
| 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 | } |
| 653 | 757 | ||
| ... | @@ -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 | } |
| 738 | 837 | ||
| 838 | pub 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 | |||
| 739 | pub fn client_connect( | 987 | pub 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 | } |
| 763 | 1014 | ||
| 1015 | const suites = if (!@hasField(Options, "ciphersuites")) | ||
| 1016 | ciphersuites.all | ||
| 1017 | else | ||
| 1018 | options.ciphersuites; | ||
| 1019 | if (suites.len == 0) | ||
| 1020 | @compileError("Must provide at least one ciphersuite type."); | ||
| 1021 | |||
| 1022 | const curvelist = if (!@hasField(Options, "curves")) | ||
| 1023 | curves.all | ||
| 1024 | else | ||
| 1025 | options.curves; | ||
| 1026 | if (curvelist.len == 0) | ||
| 1027 | @compileError("Must provide at least one curve type."); | ||
| 1028 | |||
| 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); |
| 778 | 1043 | ||
| 779 | var server_random: [32]u8 = undefined; | 1044 | var server_random: [32]u8 = undefined; |
| 780 | 1045 | 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_SCSV | 1073 | // 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 here | 1076 | // 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 | }; |
| 839 | 1101 | ||
| 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 format | 1143 | // Extension: EC point formats => uncompressed point format |
| 867 | 0x00, 0x0B, 0x00, 0x02, 0x01, 0x00, | 1144 | 0x00, 0x0B, 0x00, 0x02, 0x01, 0x00, |
| 868 | // Extension: Signature algorithms | 1145 | // Extension: Signature algorithms |
| 869 | // RSA/PKCS1/SHA256, RSA/PKCS1/SHA512 | 1146 | // 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 connection | 1149 | // 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 | }); |
| 878 | 1156 | ||
| 879 | // Read server hello | 1157 | // 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 stage1 | 1184 | // 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 key | 1282 | // 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; |
| 1014 | 1294 | ||
| 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 | .x25519 | 1299 | 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 | .secp384r1 | 1301 | inline for (curvelist) |curve| { |
| 1022 | else | 1302 | 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 | } |
| 1025 | 1311 | ||
| 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) or | 1313 | 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 | } | ||
| 1033 | 1327 | ||
| 1034 | // Signed public key | 1328 | // 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; |
| 1084 | 1374 | ||
| 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 done | 1378 | // Read server hello done |
| 1089 | { | 1379 | { |
| ... | @@ -1094,60 +1384,38 @@ pub fn client_connect( | ... | @@ -1094,60 +1384,38 @@ pub fn client_connect( |
| 1094 | } | 1384 | } |
| 1095 | 1385 | ||
| 1096 | // Generate keys for the session | 1386 | // 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 | }; | ||
| 1113 | 1388 | ||
| 1114 | // Client key exchange | 1389 | // 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 hash | 1407 | // 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 | ) catch | 1417 | 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 | }; | ||
| 1151 | 1419 | ||
| 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 | }; |
| 1211 | 1479 | ||
| 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 | } |
| 1214 | 1482 | ||
| 1215 | // Client change cipher spec and client handshake finished | 1483 | // 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); |
| 1247 | 1515 | ||
| 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 | } |
| 1287 | 1555 | ||
| 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 | } |
| 1295 | 1563 | ||
| 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 {}; |
| 1425 | 1694 | ||
| ... | @@ -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 {}; |
| 1478 | 1747 | ||
| 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" { |
| 1497 | 1766 | ||
| 1498 | try client.reader().readNoEof(html_contents); | 1767 | try client.reader().readNoEof(html_contents); |
| 1499 | } | 1768 | } |
| 1769 | |||
| 1770 | test "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 | |||
| 1795 | test "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 | |||
| 1820 | test "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: on | 21 | // zig fmt: on |
| 22 | 22 | ||
| 23 | pub const PublicKey = union(enum) { | 23 | pub const PublicKey = union(enum) { |
| 24 | pub const empty = PublicKey{ .ec = .{ .id = undefined, .curve_point = &[0]u8{} } }; | ||
| 25 | |||
| 24 | /// RSA public key | 26 | /// 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 | } |
| 46 | 48 | ||
| 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) and | 53 | .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 if | 69 | // @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 | else | 135 | 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]); |
| 154 | 155 | ||
| 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 { |
| 199 | 200 | ||
| 200 | const data = object_id.object_identifier.data; | 201 | const data = object_id.object_identifier.data; |
| 201 | // Basic constraints extension | 202 | // 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; |
| 204 | 205 | ||
| 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 | } |
| 211 | 219 | ||
| 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); |
| 347 | 355 | 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 | }; |
| 368 | 366 | ||
| 369 | fn PEMSectionReader(comptime Reader: type) type { | 367 | fn 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 | } |
| 382 | 377 | ||
| 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 | } |
| 387 | 382 | ||
| 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; | ||
| 392 | 389 | ||
| 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 | }; |
| 407 | 397 | ||
| 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 | } | ||
| 439 | 416 | ||
| 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 { |
| 444 | 421 | ||
| 445 | const out_len = std.base64.standard_decoder.calcSize(&base64_buf) catch | 422 | 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 | else | 427 | 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 { |
| 455 | 433 | ||
| 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 | } |
| 461 | 439 | ||
| 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 | } |
| 474 | 457 | ||
| 475 | fn PEMCertificateIterator(comptime Reader: type) type { | 458 | fn 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 { |
| 479 | 462 | ||
| 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, | ||
| 492 | 472 | ||
| 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 | }; |
| 528 | 484 | ||
| 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; |
| 552 | 490 | }, | |
| 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" { |
| 696 | 624 | ||
| 697 | // Try reading byte by byte from a cert reader | 625 | // 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 next | 629 | // 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 | } |
| 726 | 653 | ||
| 727 | test "TrustAnchorChain" { | 654 | test "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----- | ||
| 2 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh | ||
| 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 | ||
| 4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD | ||
| 5 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT | ||
| 6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j | ||
| 7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG | ||
| 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB | ||
| 9 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 | ||
| 10 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt | ||
| 11 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P | ||
| 12 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 | ||
| 13 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO | ||
| 14 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR | ||
| 15 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw | ||
| 16 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr | ||
| 17 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg | ||
| 18 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF | ||
| 19 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls | ||
| 20 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk | ||
| 21 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= | ||
| 22 | -----END CERTIFICATE----- | ||