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