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