├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── bench ├── bench.zig ├── build.zig └── record_handshake.zig ├── build.zig ├── gyro.zzz ├── src ├── asn1.zig ├── ciphersuites.zig ├── crypto.zig ├── main.zig ├── pcks1-1_5.zig └── x509.zig ├── test ├── DSTRootCAX3.crt.pem ├── DigiCertGlobalRootCA.crt.pem ├── DigiCertHighAssuranceEVRootCA.crt.pem ├── badssl.com-client.pem ├── github.der └── github.pem └── zig.mod /.gitattributes: -------------------------------------------------------------------------------- 1 | *.zig text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /zig-cache 2 | deps.zig 3 | gyro.lock 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexnask/iguanaTLS/0d39a361639ad5469f8e4dcdaea35446bbe54b48/.gitmodules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 22 | -------------------------------------------------------------------------------- /bench/bench.zig: -------------------------------------------------------------------------------- 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 | } 426 | -------------------------------------------------------------------------------- /bench/build.zig: -------------------------------------------------------------------------------- 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 | } 34 | -------------------------------------------------------------------------------- /bench/record_handshake.zig: -------------------------------------------------------------------------------- 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 | } 250 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 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 | } 15 | -------------------------------------------------------------------------------- /gyro.zzz: -------------------------------------------------------------------------------- 1 | pkgs: 2 | iguanaTLS: 3 | version: 0.0.3 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 | bench/*.zig 15 | test/* 16 | -------------------------------------------------------------------------------- /src/asn1.zig: -------------------------------------------------------------------------------- 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 => 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 TagLength{ .tag = tag_byte, .length = length }; 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.target.cpu.arch.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 | return try parse_int_with_length_internal(alloc, bytes_read, length, der_reader); 413 | } 414 | 415 | pub fn parse_int(alloc: *Allocator, der_reader: anytype) !BigInt { 416 | var bytes: usize = undefined; 417 | return try parse_int_internal(alloc, &bytes, der_reader); 418 | } 419 | 420 | pub fn parse_int_with_length(alloc: *Allocator, length: usize, der_reader: anytype) !BigInt { 421 | var read: usize = 0; 422 | return try parse_int_with_length_internal(alloc, &read, length, der_reader); 423 | } 424 | 425 | fn parse_int_with_length_internal(alloc: *Allocator, bytes_read: *usize, length: usize, der_reader: anytype) !BigInt { 426 | const first_byte = try der_reader.readByte(); 427 | if (first_byte == 0x0 and length > 1) { 428 | // Positive number with highest bit set to 1 in the rest. 429 | const limb_count = std.math.divCeil(usize, length - 1, @sizeOf(usize)) catch unreachable; 430 | const limbs = try alloc.alloc(usize, limb_count); 431 | std.mem.set(usize, limbs, 0); 432 | errdefer alloc.free(limbs); 433 | 434 | var limb_ptr = @ptrCast([*]u8, limbs.ptr); 435 | try der_reader.readNoEof(limb_ptr[0 .. length - 1]); 436 | // We always reverse because the standard library big int expects little endian. 437 | mem.reverse(u8, limb_ptr[0 .. length - 1]); 438 | 439 | bytes_read.* += length; 440 | return BigInt{ .limbs = limbs, .positive = true }; 441 | } 442 | std.debug.assert(length != 0); 443 | // Write first_byte 444 | // Twos complement 445 | const limb_count = std.math.divCeil(usize, length, @sizeOf(usize)) catch unreachable; 446 | const limbs = try alloc.alloc(usize, limb_count); 447 | std.mem.set(usize, limbs, 0); 448 | errdefer alloc.free(limbs); 449 | 450 | var limb_ptr = @ptrCast([*]u8, limbs.ptr); 451 | limb_ptr[0] = first_byte & ~@as(u8, 0x80); 452 | try der_reader.readNoEof(limb_ptr[1..length]); 453 | 454 | // We always reverse because the standard library big int expects little endian. 455 | mem.reverse(u8, limb_ptr[0..length]); 456 | bytes_read.* += length; 457 | return BigInt{ .limbs = limbs, .positive = (first_byte & 0x80) == 0x00 }; 458 | } 459 | 460 | pub fn parse_length(der_reader: anytype) !usize { 461 | var bytes: usize = 0; 462 | return try parse_length_internal(&bytes, der_reader); 463 | } 464 | 465 | fn parse_length_internal(bytes_read: *usize, der_reader: anytype) !usize { 466 | const first_byte = try der_reader.readByte(); 467 | bytes_read.* += 1; 468 | if (first_byte & 0x80 == 0x00) { 469 | // 1 byte value 470 | return first_byte; 471 | } 472 | const length = @truncate(u7, first_byte); 473 | if (length > @sizeOf(usize)) 474 | @panic("DER length does not fit in usize"); 475 | 476 | var res_buf = std.mem.zeroes([@sizeOf(usize)]u8); 477 | try der_reader.readNoEof(res_buf[0..length]); 478 | bytes_read.* += length; 479 | 480 | if (std.builtin.target.cpu.arch.endian() != .Big) { 481 | mem.reverse(u8, res_buf[0..length]); 482 | } 483 | return mem.bytesToValue(usize, &res_buf); 484 | } 485 | 486 | fn parse_value_with_tag_byte( 487 | tag_byte: u8, 488 | alloc: *Allocator, 489 | bytes_read: *usize, 490 | der_reader: anytype, 491 | ) DecodeError(@TypeOf(der_reader))!Value { 492 | const tag = std.meta.intToEnum(Tag, tag_byte) catch { 493 | // tag starts with '0b10...', this is the context specific class. 494 | if (tag_byte & 0xC0 == 0x80) { 495 | const length = try parse_length_internal(bytes_read, der_reader); 496 | var cur_read_bytes: usize = 0; 497 | var child = try alloc.create(Value); 498 | errdefer alloc.destroy(child); 499 | 500 | child.* = try parse_value_internal(alloc, &cur_read_bytes, der_reader); 501 | if (cur_read_bytes != length) 502 | return error.InvalidContainerLength; 503 | bytes_read.* += length; 504 | return Value{ .context_specific = .{ .child = child, .number = tag_byte - 0xa0 } }; 505 | } 506 | 507 | return error.InvalidTag; 508 | }; 509 | switch (tag) { 510 | .bool => { 511 | if ((try der_reader.readByte()) != 0x1) 512 | return error.InvalidLength; 513 | defer bytes_read.* += 2; 514 | return Value{ .bool = (try der_reader.readByte()) != 0x0 }; 515 | }, 516 | .int => return Value{ .int = try parse_int_internal(alloc, bytes_read, der_reader) }, 517 | .bit_string => { 518 | const length = try parse_length_internal(bytes_read, der_reader); 519 | const unused_bits = try der_reader.readByte(); 520 | std.debug.assert(unused_bits < 8); 521 | const bit_count = (length - 1) * 8 - unused_bits; 522 | const bit_memory = try alloc.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable); 523 | errdefer alloc.free(bit_memory); 524 | try der_reader.readNoEof(bit_memory[0 .. length - 1]); 525 | 526 | bytes_read.* += length; 527 | return Value{ .bit_string = .{ .data = bit_memory, .bit_len = bit_count } }; 528 | }, 529 | .octet_string, .utf8_string, .printable_string, .utc_time, .ia5_string => { 530 | const length = try parse_length_internal(bytes_read, der_reader); 531 | const str_mem = try alloc.alloc(u8, length); 532 | try der_reader.readNoEof(str_mem); 533 | bytes_read.* += length; 534 | return @as(Value, switch (tag) { 535 | .octet_string => .{ .octet_string = str_mem }, 536 | .utf8_string => .{ .utf8_string = str_mem }, 537 | .printable_string => .{ .printable_string = str_mem }, 538 | .utc_time => .{ .utc_time = str_mem }, 539 | .ia5_string => .{ .ia5_string = str_mem }, 540 | else => unreachable, 541 | }); 542 | }, 543 | .@"null" => { 544 | std.debug.assert((try parse_length_internal(bytes_read, der_reader)) == 0x00); 545 | return .@"null"; 546 | }, 547 | .object_identifier => { 548 | const length = try parse_length_internal(bytes_read, der_reader); 549 | const first_byte = try der_reader.readByte(); 550 | var ret = Value{ .object_identifier = .{ .data = undefined, .len = 0 } }; 551 | ret.object_identifier.data[0] = first_byte / 40; 552 | ret.object_identifier.data[1] = first_byte % 40; 553 | 554 | var out_idx: u8 = 2; 555 | var i: usize = 0; 556 | while (i < length - 1) { 557 | var current_value: u32 = 0; 558 | var current_byte = try der_reader.readByte(); 559 | i += 1; 560 | while (current_byte & 0x80 == 0x80) : (i += 1) { 561 | // Increase the base of the previous bytes 562 | current_value *= 128; 563 | // Add the current byte in base 128 564 | current_value += @as(u32, current_byte & ~@as(u8, 0x80)) * 128; 565 | current_byte = try der_reader.readByte(); 566 | } else { 567 | current_value += current_byte; 568 | } 569 | ret.object_identifier.data[out_idx] = current_value; 570 | out_idx += 1; 571 | } 572 | ret.object_identifier.len = out_idx; 573 | std.debug.assert(out_idx <= 16); 574 | bytes_read.* += length; 575 | return ret; 576 | }, 577 | .bmp_string => { 578 | const length = try parse_length_internal(bytes_read, der_reader); 579 | const str_mem = try alloc.alloc(u16, @divExact(length, 2)); 580 | errdefer alloc.free(str_mem); 581 | 582 | for (str_mem) |*wide_char| { 583 | wide_char.* = try der_reader.readIntBig(u16); 584 | } 585 | bytes_read.* += length; 586 | return Value{ .bmp_string = str_mem }; 587 | }, 588 | .sequence, .set => { 589 | const length = try parse_length_internal(bytes_read, der_reader); 590 | var cur_read_bytes: usize = 0; 591 | var arr = std.ArrayList(Value).init(alloc); 592 | errdefer arr.deinit(); 593 | 594 | while (cur_read_bytes < length) { 595 | (try arr.addOne()).* = try parse_value_internal(alloc, &cur_read_bytes, der_reader); 596 | } 597 | if (cur_read_bytes != length) 598 | return error.InvalidContainerLength; 599 | bytes_read.* += length; 600 | 601 | return @as(Value, switch (tag) { 602 | .sequence => .{ .sequence = arr.toOwnedSlice() }, 603 | .set => .{ .set = arr.toOwnedSlice() }, 604 | else => unreachable, 605 | }); 606 | }, 607 | .context_specific => unreachable, 608 | } 609 | } 610 | 611 | fn parse_value_internal(alloc: *Allocator, bytes_read: *usize, der_reader: anytype) DecodeError(@TypeOf(der_reader))!Value { 612 | const tag_byte = try der_reader.readByte(); 613 | bytes_read.* += 1; 614 | return try parse_value_with_tag_byte(tag_byte, alloc, bytes_read, der_reader); 615 | } 616 | 617 | pub fn parse_value(alloc: *Allocator, der_reader: anytype) DecodeError(@TypeOf(der_reader))!Value { 618 | var read: usize = 0; 619 | return try parse_value_internal(alloc, &read, der_reader); 620 | } 621 | }; 622 | 623 | test "der.parse_value" { 624 | const github_der = @embedFile("../test/github.der"); 625 | var fbs = std.io.fixedBufferStream(github_der); 626 | 627 | var arena = ArenaAllocator.init(std.testing.allocator); 628 | defer arena.deinit(); 629 | 630 | _ = try der.parse_value(&arena.allocator, fbs.reader()); 631 | } 632 | -------------------------------------------------------------------------------- /src/ciphersuites.zig: -------------------------------------------------------------------------------- 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 Poly1305 = std.crypto.onetimeauth.Poly1305; 7 | const Aes128Gcm = std.crypto.aead.aes_gcm.Aes128Gcm; 8 | 9 | const main = @import("main.zig"); 10 | const RecordHeader = main.RecordHeader; 11 | 12 | pub const suites = struct { 13 | pub const ECDHE_RSA_Chacha20_Poly1305 = struct { 14 | pub const name = "ECDHE-RSA-CHACHA20-POLY1305"; 15 | pub const tag = 0xCCA8; 16 | pub const key_exchange = .ecdhe; 17 | pub const hash = .sha256; 18 | pub const prefix_data_length = 0; 19 | pub const mac_length = 16; 20 | 21 | pub const Keys = struct { 22 | client_key: [32]u8, 23 | server_key: [32]u8, 24 | client_iv: [12]u8, 25 | server_iv: [12]u8, 26 | }; 27 | 28 | pub const State = struct { 29 | mac: Poly1305, 30 | context: ChaCha20Stream.BlockVec, 31 | buf: [64]u8, 32 | }; 33 | 34 | pub fn init_state(_: [0]u8, server_seq: u64, key_data: anytype, header: RecordHeader) State { 35 | const len = header.len() - 16; 36 | var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8); 37 | mem.writeIntBig(u64, nonce[4..12], server_seq); 38 | for (nonce) |*n, i| { 39 | n.* ^= key_data.server_iv(@This())[i]; 40 | } 41 | 42 | var additional_data: [13]u8 = undefined; 43 | mem.writeIntBig(u64, additional_data[0..8], server_seq); 44 | additional_data[8..11].* = header.data[0..3].*; 45 | mem.writeIntBig(u16, additional_data[11..13], len); 46 | 47 | var c: [4]u32 = undefined; 48 | c[0] = 1; 49 | c[1] = mem.readIntLittle(u32, nonce[0..4]); 50 | c[2] = mem.readIntLittle(u32, nonce[4..8]); 51 | c[3] = mem.readIntLittle(u32, nonce[8..12]); 52 | const server_key = keyToWords(key_data.server_key(@This()).*); 53 | 54 | return .{ 55 | .mac = ChaCha20Stream.initPoly1305(key_data.server_key(@This()).*, nonce, additional_data), 56 | .context = ChaCha20Stream.initContext(server_key, c), 57 | .buf = undefined, 58 | }; 59 | } 60 | 61 | pub fn decrypt_part( 62 | key_data: anytype, 63 | record_length: usize, 64 | idx: *usize, 65 | state: *State, 66 | encrypted: []const u8, 67 | out: []u8, 68 | ) void { 69 | _ = record_length; 70 | 71 | std.debug.assert(encrypted.len == out.len); 72 | ChaCha20Stream.chacha20Xor( 73 | out, 74 | encrypted, 75 | keyToWords(key_data.server_key(@This()).*), 76 | &state.context, 77 | idx, 78 | &state.buf, 79 | ); 80 | 81 | state.mac.update(encrypted); 82 | } 83 | 84 | pub fn verify_mac(reader: anytype, record_length: usize, state: *State) !void { 85 | var poly1305_tag: [16]u8 = undefined; 86 | reader.readNoEof(&poly1305_tag) catch |err| switch (err) { 87 | error.EndOfStream => return error.ServerMalformedResponse, 88 | else => |e| return e, 89 | }; 90 | try ChaCha20Stream.checkPoly1305(&state.mac, record_length, poly1305_tag); 91 | } 92 | 93 | pub fn raw_write( 94 | comptime buffer_size: usize, 95 | rand: *std.rand.Random, 96 | key_data: anytype, 97 | writer: anytype, 98 | prefix: [3]u8, 99 | seq: u64, 100 | buffer: []const u8, 101 | ) !void { 102 | _ = rand; 103 | 104 | std.debug.assert(buffer.len <= buffer_size); 105 | try writer.writeAll(&prefix); 106 | try writer.writeIntBig(u16, @intCast(u16, buffer.len + 16)); 107 | 108 | var additional_data: [13]u8 = undefined; 109 | mem.writeIntBig(u64, additional_data[0..8], seq); 110 | additional_data[8..11].* = prefix; 111 | mem.writeIntBig(u16, additional_data[11..13], @intCast(u16, buffer.len)); 112 | 113 | var encrypted_data: [buffer_size]u8 = undefined; 114 | var tag_data: [16]u8 = undefined; 115 | 116 | var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8); 117 | mem.writeIntBig(u64, nonce[4..12], seq); 118 | for (nonce) |*n, i| { 119 | n.* ^= key_data.client_iv(@This())[i]; 120 | } 121 | 122 | Chacha20Poly1305.encrypt( 123 | encrypted_data[0..buffer.len], 124 | &tag_data, 125 | buffer, 126 | &additional_data, 127 | nonce, 128 | key_data.client_key(@This()).*, 129 | ); 130 | try writer.writeAll(encrypted_data[0..buffer.len]); 131 | try writer.writeAll(&tag_data); 132 | } 133 | 134 | pub fn check_verify_message( 135 | key_data: anytype, 136 | length: usize, 137 | reader: anytype, 138 | verify_message: [16]u8, 139 | ) !bool { 140 | if (length != 32) 141 | return false; 142 | 143 | var msg_in: [32]u8 = undefined; 144 | try reader.readNoEof(&msg_in); 145 | 146 | const additional_data: [13]u8 = ([1]u8{0} ** 8) ++ [5]u8{ 0x16, 0x03, 0x03, 0x00, 0x10 }; 147 | var decrypted: [16]u8 = undefined; 148 | Chacha20Poly1305.decrypt( 149 | &decrypted, 150 | msg_in[0..16], 151 | msg_in[16..].*, 152 | &additional_data, 153 | key_data.server_iv(@This()).*, 154 | key_data.server_key(@This()).*, 155 | ) catch return false; 156 | 157 | return mem.eql(u8, &decrypted, &verify_message); 158 | } 159 | }; 160 | 161 | pub const ECDHE_RSA_AES128_GCM_SHA256 = struct { 162 | pub const name = "ECDHE-RSA-AES128-GCM-SHA256"; 163 | pub const tag = 0xC02F; 164 | pub const key_exchange = .ecdhe; 165 | pub const hash = .sha256; 166 | pub const prefix_data_length = 8; 167 | pub const mac_length = 16; 168 | 169 | pub const Keys = struct { 170 | client_key: [16]u8, 171 | server_key: [16]u8, 172 | client_iv: [4]u8, 173 | server_iv: [4]u8, 174 | }; 175 | 176 | const Aes = std.crypto.core.aes.Aes128; 177 | pub const State = struct { 178 | aes: @typeInfo(@TypeOf(Aes.initEnc)).Fn.return_type.?, 179 | counterInt: u128, 180 | }; 181 | 182 | pub fn init_state(prefix_data: [8]u8, server_seq: u64, key_data: anytype, header: RecordHeader) State { 183 | _ = server_seq; 184 | _ = header; 185 | 186 | var iv: [12]u8 = undefined; 187 | iv[0..4].* = key_data.server_iv(@This()).*; 188 | iv[4..].* = prefix_data; 189 | 190 | var j: [16]u8 = undefined; 191 | mem.copy(u8, j[0..12], iv[0..]); 192 | mem.writeIntBig(u32, j[12..][0..4], 2); 193 | 194 | return .{ 195 | .aes = Aes.initEnc(key_data.server_key(@This()).*), 196 | .counterInt = mem.readInt(u128, &j, .Big), 197 | }; 198 | } 199 | 200 | pub fn decrypt_part( 201 | key_data: anytype, 202 | record_length: usize, 203 | idx: *usize, 204 | state: *State, 205 | encrypted: []const u8, 206 | out: []u8, 207 | ) void { 208 | _ = key_data; 209 | _ = record_length; 210 | 211 | std.debug.assert(encrypted.len == out.len); 212 | 213 | ctr( 214 | @TypeOf(state.aes), 215 | state.aes, 216 | out, 217 | encrypted, 218 | &state.counterInt, 219 | idx, 220 | .Big, 221 | ); 222 | } 223 | 224 | pub fn verify_mac(reader: anytype, record_length: usize, state: *State) !void { 225 | _ = state; 226 | _ = record_length; 227 | // @TODO Implement this 228 | reader.skipBytes(16, .{}) catch |err| switch (err) { 229 | error.EndOfStream => return error.ServerMalformedResponse, 230 | else => |e| return e, 231 | }; 232 | } 233 | 234 | pub fn check_verify_message( 235 | key_data: anytype, 236 | length: usize, 237 | reader: anytype, 238 | verify_message: [16]u8, 239 | ) !bool { 240 | if (length != 40) 241 | return false; 242 | 243 | var iv: [12]u8 = undefined; 244 | iv[0..4].* = key_data.server_iv(@This()).*; 245 | try reader.readNoEof(iv[4..12]); 246 | 247 | var msg_in: [32]u8 = undefined; 248 | try reader.readNoEof(&msg_in); 249 | 250 | const additional_data: [13]u8 = ([1]u8{0} ** 8) ++ [5]u8{ 0x16, 0x03, 0x03, 0x00, 0x10 }; 251 | var decrypted: [16]u8 = undefined; 252 | Aes128Gcm.decrypt( 253 | &decrypted, 254 | msg_in[0..16], 255 | msg_in[16..].*, 256 | &additional_data, 257 | iv, 258 | key_data.server_key(@This()).*, 259 | ) catch return false; 260 | 261 | return mem.eql(u8, &decrypted, &verify_message); 262 | } 263 | 264 | pub fn raw_write( 265 | comptime buffer_size: usize, 266 | rand: *std.rand.Random, 267 | key_data: anytype, 268 | writer: anytype, 269 | prefix: [3]u8, 270 | seq: u64, 271 | buffer: []const u8, 272 | ) !void { 273 | std.debug.assert(buffer.len <= buffer_size); 274 | var iv: [12]u8 = undefined; 275 | iv[0..4].* = key_data.client_iv(@This()).*; 276 | rand.bytes(iv[4..12]); 277 | 278 | var additional_data: [13]u8 = undefined; 279 | mem.writeIntBig(u64, additional_data[0..8], seq); 280 | additional_data[8..11].* = prefix; 281 | mem.writeIntBig(u16, additional_data[11..13], @intCast(u16, buffer.len)); 282 | 283 | try writer.writeAll(&prefix); 284 | try writer.writeIntBig(u16, @intCast(u16, buffer.len + 24)); 285 | try writer.writeAll(iv[4..12]); 286 | 287 | var encrypted_data: [buffer_size]u8 = undefined; 288 | var tag_data: [16]u8 = undefined; 289 | 290 | Aes128Gcm.encrypt( 291 | encrypted_data[0..buffer.len], 292 | &tag_data, 293 | buffer, 294 | &additional_data, 295 | iv, 296 | key_data.client_key(@This()).*, 297 | ); 298 | try writer.writeAll(encrypted_data[0..buffer.len]); 299 | try writer.writeAll(&tag_data); 300 | } 301 | }; 302 | 303 | pub const all = &[_]type{ ECDHE_RSA_Chacha20_Poly1305, ECDHE_RSA_AES128_GCM_SHA256 }; 304 | }; 305 | 306 | fn key_field_width(comptime T: type, comptime field: anytype) ?usize { 307 | if (!@hasField(T, @tagName(field))) 308 | return null; 309 | 310 | const field_info = std.meta.fieldInfo(T, field); 311 | if (!comptime std.meta.trait.is(.Array)(field_info.field_type) or std.meta.Elem(field_info.field_type) != u8) 312 | @compileError("Field '" ++ field ++ "' of type '" ++ @typeName(T) ++ "' should be an array of u8."); 313 | 314 | return @typeInfo(field_info.field_type).Array.len; 315 | } 316 | 317 | pub fn key_data_size(comptime ciphersuites: anytype) usize { 318 | var max: usize = 0; 319 | for (ciphersuites) |cs| { 320 | const curr = (key_field_width(cs.Keys, .client_mac) orelse 0) + 321 | (key_field_width(cs.Keys, .server_mac) orelse 0) + 322 | key_field_width(cs.Keys, .client_key).? + 323 | key_field_width(cs.Keys, .server_key).? + 324 | key_field_width(cs.Keys, .client_iv).? + 325 | key_field_width(cs.Keys, .server_iv).?; 326 | if (curr > max) 327 | max = curr; 328 | } 329 | return max; 330 | } 331 | 332 | pub fn KeyData(comptime ciphersuites: anytype) type { 333 | return struct { 334 | data: [key_data_size(ciphersuites)]u8, 335 | 336 | pub fn client_mac(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_mac) orelse 0]u8 { 337 | return self.data[0..comptime (key_field_width(cs.Keys, .client_mac) orelse 0)]; 338 | } 339 | 340 | pub fn server_mac(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_mac) orelse 0]u8 { 341 | const start = key_field_width(cs.Keys, .client_mac) orelse 0; 342 | return self.data[start..][0..comptime (key_field_width(cs.Keys, .server_mac) orelse 0)]; 343 | } 344 | 345 | pub fn client_key(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_key).?]u8 { 346 | const start = (key_field_width(cs.Keys, .client_mac) orelse 0) + 347 | (key_field_width(cs.Keys, .server_mac) orelse 0); 348 | return self.data[start..][0..comptime key_field_width(cs.Keys, .client_key).?]; 349 | } 350 | 351 | pub fn server_key(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_key).?]u8 { 352 | const start = (key_field_width(cs.Keys, .client_mac) orelse 0) + 353 | (key_field_width(cs.Keys, .server_mac) orelse 0) + 354 | key_field_width(cs.Keys, .client_key).?; 355 | return self.data[start..][0..comptime key_field_width(cs.Keys, .server_key).?]; 356 | } 357 | 358 | pub fn client_iv(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_iv).?]u8 { 359 | const start = (key_field_width(cs.Keys, .client_mac) orelse 0) + 360 | (key_field_width(cs.Keys, .server_mac) orelse 0) + 361 | key_field_width(cs.Keys, .client_key).? + 362 | key_field_width(cs.Keys, .server_key).?; 363 | return self.data[start..][0..comptime key_field_width(cs.Keys, .client_iv).?]; 364 | } 365 | 366 | pub fn server_iv(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_iv).?]u8 { 367 | const start = (key_field_width(cs.Keys, .client_mac) orelse 0) + 368 | (key_field_width(cs.Keys, .server_mac) orelse 0) + 369 | key_field_width(cs.Keys, .client_key).? + 370 | key_field_width(cs.Keys, .server_key).? + 371 | key_field_width(cs.Keys, .client_iv).?; 372 | return self.data[start..][0..comptime key_field_width(cs.Keys, .server_iv).?]; 373 | } 374 | }; 375 | } 376 | 377 | pub fn key_expansion( 378 | comptime ciphersuites: anytype, 379 | tag: u16, 380 | context: anytype, 381 | comptime next_32_bytes: anytype, 382 | ) KeyData(ciphersuites) { 383 | var res: KeyData(ciphersuites) = undefined; 384 | inline for (ciphersuites) |cs| { 385 | if (cs.tag == tag) { 386 | var chunk: [32]u8 = undefined; 387 | next_32_bytes(context, 0, &chunk); 388 | comptime var chunk_idx = 1; 389 | comptime var data_cursor = 0; 390 | comptime var chunk_cursor = 0; 391 | 392 | const fields = .{ 393 | .client_mac, .server_mac, 394 | .client_key, .server_key, 395 | .client_iv, .server_iv, 396 | }; 397 | inline for (fields) |field| { 398 | if (chunk_cursor == 32) { 399 | next_32_bytes(context, chunk_idx, &chunk); 400 | chunk_idx += 1; 401 | chunk_cursor = 0; 402 | } 403 | 404 | const field_width = comptime (key_field_width(cs.Keys, field) orelse 0); 405 | const first_read = comptime std.math.min(32 - chunk_cursor, field_width); 406 | const second_read = field_width - first_read; 407 | 408 | res.data[data_cursor..][0..first_read].* = chunk[chunk_cursor..][0..first_read].*; 409 | data_cursor += first_read; 410 | chunk_cursor += first_read; 411 | 412 | if (second_read != 0) { 413 | next_32_bytes(context, chunk_idx, &chunk); 414 | chunk_idx += 1; 415 | res.data[data_cursor..][0..second_read].* = chunk[chunk_cursor..][0..second_read].*; 416 | data_cursor += second_read; 417 | chunk_cursor = second_read; 418 | comptime std.debug.assert(chunk_cursor != 32); 419 | } 420 | } 421 | 422 | return res; 423 | } 424 | } 425 | unreachable; 426 | } 427 | 428 | pub fn InRecordState(comptime ciphersuites: anytype) type { 429 | var fields: [ciphersuites.len]std.builtin.TypeInfo.UnionField = undefined; 430 | for (ciphersuites) |cs, i| { 431 | fields[i] = .{ 432 | .name = cs.name, 433 | .field_type = cs.State, 434 | .alignment = if (@sizeOf(cs.State) > 0) @alignOf(cs.State) else 0, 435 | }; 436 | } 437 | return @Type(.{ 438 | .Union = .{ 439 | .layout = .Extern, 440 | .tag_type = null, 441 | .fields = &fields, 442 | .decls = &[0]std.builtin.TypeInfo.Declaration{}, 443 | }, 444 | }); 445 | } 446 | -------------------------------------------------------------------------------- /src/crypto.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | 4 | const Poly1305 = std.crypto.onetimeauth.Poly1305; 5 | const Chacha20IETF = std.crypto.stream.chacha.ChaCha20IETF; 6 | 7 | // TODO See stdlib, this is a modified non vectorized implementation 8 | pub const ChaCha20Stream = struct { 9 | const math = std.math; 10 | pub const BlockVec = [16]u32; 11 | 12 | pub fn initContext(key: [8]u32, d: [4]u32) BlockVec { 13 | const c = "expand 32-byte k"; 14 | const constant_le = comptime [4]u32{ 15 | mem.readIntLittle(u32, c[0..4]), 16 | mem.readIntLittle(u32, c[4..8]), 17 | mem.readIntLittle(u32, c[8..12]), 18 | mem.readIntLittle(u32, c[12..16]), 19 | }; 20 | return BlockVec{ 21 | constant_le[0], constant_le[1], constant_le[2], constant_le[3], 22 | key[0], key[1], key[2], key[3], 23 | key[4], key[5], key[6], key[7], 24 | d[0], d[1], d[2], d[3], 25 | }; 26 | } 27 | 28 | const QuarterRound = struct { 29 | a: usize, 30 | b: usize, 31 | c: usize, 32 | d: usize, 33 | }; 34 | 35 | fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { 36 | return QuarterRound{ 37 | .a = a, 38 | .b = b, 39 | .c = c, 40 | .d = d, 41 | }; 42 | } 43 | 44 | inline fn chacha20Core(x: *BlockVec, input: BlockVec) void { 45 | x.* = input; 46 | 47 | const rounds = comptime [_]QuarterRound{ 48 | Rp(0, 4, 8, 12), 49 | Rp(1, 5, 9, 13), 50 | Rp(2, 6, 10, 14), 51 | Rp(3, 7, 11, 15), 52 | Rp(0, 5, 10, 15), 53 | Rp(1, 6, 11, 12), 54 | Rp(2, 7, 8, 13), 55 | Rp(3, 4, 9, 14), 56 | }; 57 | 58 | comptime var j: usize = 0; 59 | inline while (j < 20) : (j += 2) { 60 | inline for (rounds) |r| { 61 | x[r.a] +%= x[r.b]; 62 | x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16)); 63 | x[r.c] +%= x[r.d]; 64 | x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12)); 65 | x[r.a] +%= x[r.b]; 66 | x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8)); 67 | x[r.c] +%= x[r.d]; 68 | x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7)); 69 | } 70 | } 71 | } 72 | 73 | inline fn hashToBytes(out: *[64]u8, x: BlockVec) void { 74 | var i: usize = 0; 75 | while (i < 4) : (i += 1) { 76 | mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]); 77 | mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]); 78 | mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]); 79 | mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]); 80 | } 81 | } 82 | 83 | inline fn contextFeedback(x: *BlockVec, ctx: BlockVec) void { 84 | var i: usize = 0; 85 | while (i < 16) : (i += 1) { 86 | x[i] +%= ctx[i]; 87 | } 88 | } 89 | 90 | pub fn initPoly1305(key: [32]u8, nonce: [12]u8, ad: [13]u8) Poly1305 { 91 | var polyKey = [_]u8{0} ** 32; 92 | Chacha20IETF.xor(&polyKey, &polyKey, 0, key, nonce); 93 | var mac = Poly1305.init(&polyKey); 94 | mac.update(&ad); 95 | // Pad to 16 bytes from ad 96 | mac.update(&.{ 0, 0, 0 }); 97 | return mac; 98 | } 99 | 100 | /// Call after `mac` has been updated with the whole message 101 | pub fn checkPoly1305(mac: *Poly1305, len: usize, tag: [16]u8) !void { 102 | if (len % 16 != 0) { 103 | const zeros = [_]u8{0} ** 16; 104 | const padding = 16 - (len % 16); 105 | mac.update(zeros[0..padding]); 106 | } 107 | var lens: [16]u8 = undefined; 108 | mem.writeIntLittle(u64, lens[0..8], 13); 109 | mem.writeIntLittle(u64, lens[8..16], len); 110 | mac.update(lens[0..]); 111 | var computedTag: [16]u8 = undefined; 112 | mac.final(computedTag[0..]); 113 | 114 | var acc: u8 = 0; 115 | for (computedTag) |_, i| { 116 | acc |= computedTag[i] ^ tag[i]; 117 | } 118 | if (acc != 0) { 119 | return error.AuthenticationFailed; 120 | } 121 | } 122 | 123 | // TODO: Optimize this 124 | pub fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, ctx: *BlockVec, idx: *usize, buf: *[64]u8) void { 125 | _ = key; 126 | 127 | var x: BlockVec = undefined; 128 | 129 | var i: usize = 0; 130 | while (i < in.len) { 131 | if (idx.* % 64 == 0) { 132 | if (idx.* != 0) { 133 | ctx.*[12] += 1; 134 | } 135 | chacha20Core(x[0..], ctx.*); 136 | contextFeedback(&x, ctx.*); 137 | hashToBytes(buf, x); 138 | } 139 | 140 | out[i] = in[i] ^ buf[idx.* % 64]; 141 | 142 | i += 1; 143 | idx.* += 1; 144 | } 145 | } 146 | }; 147 | 148 | pub fn keyToWords(key: [32]u8) [8]u32 { 149 | var k: [8]u32 = undefined; 150 | var i: usize = 0; 151 | while (i < 8) : (i += 1) { 152 | k[i] = mem.readIntLittle(u32, key[i * 4 ..][0..4]); 153 | } 154 | return k; 155 | } 156 | 157 | // See std.crypto.core.modes.ctr 158 | /// This mode creates a key stream by encrypting an incrementing counter using a block cipher, and adding it to the source material. 159 | pub fn ctr( 160 | comptime BlockCipher: anytype, 161 | block_cipher: BlockCipher, 162 | dst: []u8, 163 | src: []const u8, 164 | counterInt: *u128, 165 | idx: *usize, 166 | endian: std.builtin.Endian, 167 | ) void { 168 | std.debug.assert(dst.len >= src.len); 169 | const block_length = BlockCipher.block_length; 170 | var cur_idx: usize = 0; 171 | 172 | const offset = idx.* % block_length; 173 | if (offset != 0) { 174 | const part_len = std.math.min(block_length - offset, src.len); 175 | 176 | var counter: [BlockCipher.block_length]u8 = undefined; 177 | mem.writeInt(u128, &counter, counterInt.*, endian); 178 | var pad = [_]u8{0} ** block_length; 179 | mem.copy(u8, pad[offset..], src[0..part_len]); 180 | block_cipher.xor(&pad, &pad, counter); 181 | mem.copy(u8, dst[0..part_len], pad[offset..][0..part_len]); 182 | cur_idx += part_len; 183 | idx.* += part_len; 184 | if (idx.* % block_length == 0) 185 | counterInt.* += 1; 186 | } 187 | 188 | const start_idx = cur_idx; 189 | const remaining = src.len - cur_idx; 190 | cur_idx = 0; 191 | 192 | const parallel_count = BlockCipher.block.parallel.optimal_parallel_blocks; 193 | const wide_block_length = parallel_count * 16; 194 | if (remaining >= wide_block_length) { 195 | var counters: [parallel_count * 16]u8 = undefined; 196 | while (cur_idx + wide_block_length <= remaining) : (cur_idx += wide_block_length) { 197 | comptime var j = 0; 198 | inline while (j < parallel_count) : (j += 1) { 199 | mem.writeInt(u128, counters[j * 16 .. j * 16 + 16], counterInt.*, endian); 200 | counterInt.* +%= 1; 201 | } 202 | 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); 203 | idx.* += wide_block_length; 204 | } 205 | } 206 | while (cur_idx + block_length <= remaining) : (cur_idx += block_length) { 207 | var counter: [BlockCipher.block_length]u8 = undefined; 208 | mem.writeInt(u128, &counter, counterInt.*, endian); 209 | counterInt.* +%= 1; 210 | 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); 211 | idx.* += block_length; 212 | } 213 | if (cur_idx < remaining) { 214 | std.debug.assert(idx.* % block_length == 0); 215 | var counter: [BlockCipher.block_length]u8 = undefined; 216 | mem.writeInt(u128, &counter, counterInt.*, endian); 217 | 218 | var pad = [_]u8{0} ** block_length; 219 | mem.copy(u8, &pad, src[start_idx..][cur_idx..]); 220 | block_cipher.xor(&pad, &pad, counter); 221 | mem.copy(u8, dst[start_idx..][cur_idx..], pad[0 .. remaining - cur_idx]); 222 | 223 | idx.* += remaining - cur_idx; 224 | if (idx.* % block_length == 0) 225 | counterInt.* +%= 1; 226 | } 227 | } 228 | 229 | // Ported from BearSSL's ec_prime_i31 engine 230 | pub const ecc = struct { 231 | pub const SECP384R1 = struct { 232 | pub const point_len = 96; 233 | 234 | const order = [point_len / 2]u8{ 235 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 236 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 237 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 238 | 0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF, 239 | 0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A, 240 | 0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73, 241 | }; 242 | 243 | const P = [_]u32{ 244 | 0x0000018C, 0x7FFFFFFF, 0x00000001, 0x00000000, 245 | 0x7FFFFFF8, 0x7FFFFFEF, 0x7FFFFFFF, 0x7FFFFFFF, 246 | 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 247 | 0x7FFFFFFF, 0x00000FFF, 248 | }; 249 | const R2 = [_]u32{ 250 | 0x0000018C, 0x00000000, 0x00000080, 0x7FFFFE00, 251 | 0x000001FF, 0x00000800, 0x00000000, 0x7FFFE000, 252 | 0x00001FFF, 0x00008000, 0x00008000, 0x00000000, 253 | 0x00000000, 0x00000000, 254 | }; 255 | const B = [_]u32{ 256 | 0x0000018C, 0x6E666840, 0x070D0392, 0x5D810231, 257 | 0x7651D50C, 0x17E218D6, 0x1B192002, 0x44EFE441, 258 | 0x3A524E2B, 0x2719BA5F, 0x41F02209, 0x36C5643E, 259 | 0x5813EFFE, 0x000008A5, 260 | }; 261 | 262 | const base_point = [point_len]u8{ 263 | 0xAA, 0x87, 0xCA, 0x22, 0xBE, 0x8B, 0x05, 0x37, 264 | 0x8E, 0xB1, 0xC7, 0x1E, 0xF3, 0x20, 0xAD, 0x74, 265 | 0x6E, 0x1D, 0x3B, 0x62, 0x8B, 0xA7, 0x9B, 0x98, 266 | 0x59, 0xF7, 0x41, 0xE0, 0x82, 0x54, 0x2A, 0x38, 267 | 0x55, 0x02, 0xF2, 0x5D, 0xBF, 0x55, 0x29, 0x6C, 268 | 0x3A, 0x54, 0x5E, 0x38, 0x72, 0x76, 0x0A, 0xB7, 269 | 0x36, 0x17, 0xDE, 0x4A, 0x96, 0x26, 0x2C, 0x6F, 270 | 0x5D, 0x9E, 0x98, 0xBF, 0x92, 0x92, 0xDC, 0x29, 271 | 0xF8, 0xF4, 0x1D, 0xBD, 0x28, 0x9A, 0x14, 0x7C, 272 | 0xE9, 0xDA, 0x31, 0x13, 0xB5, 0xF0, 0xB8, 0xC0, 273 | 0x0A, 0x60, 0xB1, 0xCE, 0x1D, 0x7E, 0x81, 0x9D, 274 | 0x7A, 0x43, 0x1D, 0x7C, 0x90, 0xEA, 0x0E, 0x5F, 275 | }; 276 | 277 | comptime { 278 | std.debug.assert((P[0] - (P[0] >> 5) + 7) >> 2 == point_len + 1); 279 | } 280 | }; 281 | 282 | pub const SECP256R1 = struct { 283 | pub const point_len = 64; 284 | 285 | const order = [point_len / 2]u8{ 286 | 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 287 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 288 | 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, 289 | 0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, 290 | }; 291 | 292 | const P = [_]u32{ 293 | 0x00000108, 0x7FFFFFFF, 294 | 0x7FFFFFFF, 0x7FFFFFFF, 295 | 0x00000007, 0x00000000, 296 | 0x00000000, 0x00000040, 297 | 0x7FFFFF80, 0x000000FF, 298 | }; 299 | const R2 = [_]u32{ 300 | 0x00000108, 0x00014000, 301 | 0x00018000, 0x00000000, 302 | 0x7FF40000, 0x7FEFFFFF, 303 | 0x7FF7FFFF, 0x7FAFFFFF, 304 | 0x005FFFFF, 0x00000000, 305 | }; 306 | const B = [_]u32{ 307 | 0x00000108, 0x6FEE1803, 308 | 0x6229C4BD, 0x21B139BE, 309 | 0x327150AA, 0x3567802E, 310 | 0x3F7212ED, 0x012E4355, 311 | 0x782DD38D, 0x0000000E, 312 | }; 313 | 314 | const base_point = [point_len]u8{ 315 | 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 316 | 0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40, 0xF2, 317 | 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 318 | 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96, 319 | 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 320 | 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 321 | 0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE, 322 | 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5, 323 | }; 324 | 325 | comptime { 326 | std.debug.assert((P[0] - (P[0] >> 5) + 7) >> 2 == point_len + 1); 327 | } 328 | }; 329 | 330 | fn jacobian_len(comptime Curve: type) usize { 331 | return @divTrunc(Curve.order.len * 8 + 61, 31); 332 | } 333 | 334 | fn Jacobian(comptime Curve: type) type { 335 | return [3][jacobian_len(Curve)]u32; 336 | } 337 | 338 | fn zero_jacobian(comptime Curve: type) Jacobian(Curve) { 339 | var result = std.mem.zeroes(Jacobian(Curve)); 340 | result[0][0] = Curve.P[0]; 341 | result[1][0] = Curve.P[0]; 342 | result[2][0] = Curve.P[0]; 343 | return result; 344 | } 345 | 346 | pub fn scalarmult( 347 | comptime Curve: type, 348 | point: [Curve.point_len]u8, 349 | k: []const u8, 350 | ) ![Curve.point_len]u8 { 351 | var P: Jacobian(Curve) = undefined; 352 | var res: u32 = decode_to_jacobian(Curve, &P, point); 353 | point_mul(Curve, &P, k); 354 | var out: [Curve.point_len]u8 = undefined; 355 | encode_from_jacobian(Curve, &out, P); 356 | if (res == 0) 357 | return error.MultiplicationFailed; 358 | return out; 359 | } 360 | 361 | pub fn KeyPair(comptime Curve: type) type { 362 | return struct { 363 | public_key: [Curve.point_len]u8, 364 | secret_key: [Curve.point_len / 2]u8, 365 | }; 366 | } 367 | 368 | pub fn make_key_pair(comptime Curve: type, rand_bytes: [Curve.point_len / 2]u8) KeyPair(Curve) { 369 | var key_bytes = rand_bytes; 370 | comptime var mask: u8 = 0xFF; 371 | comptime { 372 | while (mask >= Curve.order[0]) { 373 | mask >>= 1; 374 | } 375 | } 376 | key_bytes[0] &= mask; 377 | key_bytes[Curve.point_len / 2 - 1] |= 0x01; 378 | 379 | return .{ 380 | .secret_key = key_bytes, 381 | .public_key = scalarmult(Curve, Curve.base_point, &key_bytes) catch unreachable, 382 | }; 383 | } 384 | 385 | fn jacobian_with_one_set(comptime Curve: type, comptime fields: [2][jacobian_len(Curve)]u32) Jacobian(Curve) { 386 | const plen = comptime (Curve.P[0] + 63) >> 5; 387 | return fields ++ [1][jacobian_len(Curve)]u32{ 388 | [2]u32{ Curve.P[0], 1 } ++ ([1]u32{0} ** (plen - 2)), 389 | }; 390 | } 391 | 392 | fn encode_from_jacobian(comptime Curve: type, point: *[Curve.point_len]u8, P: Jacobian(Curve)) void { 393 | var Q = P; 394 | const T = comptime jacobian_with_one_set(Curve, [2][jacobian_len(Curve)]u32{ undefined, undefined }); 395 | _ = run_code(Curve, &Q, T, &code.affine); 396 | encode_jacobian_part(point[0 .. Curve.point_len / 2], &Q[0]); 397 | encode_jacobian_part(point[Curve.point_len / 2 ..], &Q[1]); 398 | } 399 | 400 | fn point_mul(comptime Curve: type, P: *Jacobian(Curve), x: []const u8) void { 401 | var P2 = P.*; 402 | point_double(Curve, &P2); 403 | var P3 = P.*; 404 | point_add(Curve, &P3, P2); 405 | var Q = zero_jacobian(Curve); 406 | var qz: u32 = 1; 407 | var xlen = x.len; 408 | var xidx: usize = 0; 409 | while (xlen > 0) : ({ 410 | xlen -= 1; 411 | xidx += 1; 412 | }) { 413 | var k: u3 = 6; 414 | while (true) : (k -= 2) { 415 | point_double(Curve, &Q); 416 | point_double(Curve, &Q); 417 | var T = P.*; 418 | var U = Q; 419 | const bits = @as(u32, x[xidx] >> k) & 3; 420 | const bnz = NEQ(bits, 0); 421 | CCOPY(EQ(bits, 2), mem.asBytes(&T), mem.asBytes(&P2)); 422 | CCOPY(EQ(bits, 3), mem.asBytes(&T), mem.asBytes(&P3)); 423 | point_add(Curve, &U, T); 424 | CCOPY(bnz & qz, mem.asBytes(&Q), mem.asBytes(&T)); 425 | CCOPY(bnz & ~qz, mem.asBytes(&Q), mem.asBytes(&U)); 426 | qz &= ~bnz; 427 | 428 | if (k == 0) 429 | break; 430 | } 431 | } 432 | P.* = Q; 433 | } 434 | 435 | inline fn point_double(comptime Curve: type, P: *Jacobian(Curve)) void { 436 | _ = run_code(Curve, P, P.*, &code.double); 437 | } 438 | inline fn point_add(comptime Curve: type, P1: *Jacobian(Curve), P2: Jacobian(Curve)) void { 439 | _ = run_code(Curve, P1, P2, &code._add); 440 | } 441 | 442 | fn decode_to_jacobian( 443 | comptime Curve: type, 444 | out: *Jacobian(Curve), 445 | point: [Curve.point_len]u8, 446 | ) u32 { 447 | out.* = zero_jacobian(Curve); 448 | var result = decode_mod(Curve, &out.*[0], point[0 .. Curve.point_len / 2].*); 449 | result &= decode_mod(Curve, &out.*[1], point[Curve.point_len / 2 ..].*); 450 | 451 | const zlen = comptime ((Curve.P[0] + 63) >> 5); 452 | comptime std.debug.assert(zlen == @typeInfo(@TypeOf(Curve.R2)).Array.len); 453 | comptime std.debug.assert(zlen == @typeInfo(@TypeOf(Curve.B)).Array.len); 454 | 455 | const Q = comptime jacobian_with_one_set(Curve, [2][jacobian_len(Curve)]u32{ Curve.R2, Curve.B }); 456 | result &= ~run_code(Curve, out, Q, &code.check); 457 | return result; 458 | } 459 | 460 | const code = struct { 461 | const P1x = 0; 462 | const P1y = 1; 463 | const P1z = 2; 464 | const P2x = 3; 465 | const P2y = 4; 466 | const P2z = 5; 467 | const Px = 0; 468 | const Py = 1; 469 | const Pz = 2; 470 | const t1 = 6; 471 | const t2 = 7; 472 | const t3 = 8; 473 | const t4 = 9; 474 | const t5 = 10; 475 | const t6 = 11; 476 | const t7 = 12; 477 | const t8 = 3; 478 | const t9 = 4; 479 | const t10 = 5; 480 | fn MSET(comptime d: u16, comptime a: u16) u16 { 481 | return 0x0000 + (d << 8) + (a << 4); 482 | } 483 | fn MADD(comptime d: u16, comptime a: u16) u16 { 484 | return 0x1000 + (d << 8) + (a << 4); 485 | } 486 | fn MSUB(comptime d: u16, comptime a: u16) u16 { 487 | return 0x2000 + (d << 8) + (a << 4); 488 | } 489 | fn MMUL(comptime d: u16, comptime a: u16, comptime b: u16) u16 { 490 | return 0x3000 + (d << 8) + (a << 4) + b; 491 | } 492 | fn MINV(comptime d: u16, comptime a: u16, comptime b: u16) u16 { 493 | return 0x4000 + (d << 8) + (a << 4) + b; 494 | } 495 | fn MTZ(comptime d: u16) u16 { 496 | return 0x5000 + (d << 8); 497 | } 498 | const ENDCODE = 0; 499 | 500 | const check = [_]u16{ 501 | // Convert x and y to Montgomery representation. 502 | MMUL(t1, P1x, P2x), 503 | MMUL(t2, P1y, P2x), 504 | MSET(P1x, t1), 505 | MSET(P1y, t2), 506 | // Compute x^3 in t1. 507 | MMUL(t2, P1x, P1x), 508 | MMUL(t1, P1x, t2), 509 | // Subtract 3*x from t1. 510 | MSUB(t1, P1x), 511 | MSUB(t1, P1x), 512 | MSUB(t1, P1x), 513 | // Add b. 514 | MADD(t1, P2y), 515 | // Compute y^2 in t2. 516 | MMUL(t2, P1y, P1y), 517 | // Compare y^2 with x^3 - 3*x + b; they must match. 518 | MSUB(t1, t2), 519 | MTZ(t1), 520 | // Set z to 1 (in Montgomery representation). 521 | MMUL(P1z, P2x, P2z), 522 | ENDCODE, 523 | }; 524 | const double = [_]u16{ 525 | // Compute z^2 (in t1). 526 | MMUL(t1, Pz, Pz), 527 | // Compute x-z^2 (in t2) and then x+z^2 (in t1). 528 | MSET(t2, Px), 529 | MSUB(t2, t1), 530 | MADD(t1, Px), 531 | // Compute m = 3*(x+z^2)*(x-z^2) (in t1). 532 | MMUL(t3, t1, t2), 533 | MSET(t1, t3), 534 | MADD(t1, t3), 535 | MADD(t1, t3), 536 | // Compute s = 4*x*y^2 (in t2) and 2*y^2 (in t3). 537 | MMUL(t3, Py, Py), 538 | MADD(t3, t3), 539 | MMUL(t2, Px, t3), 540 | MADD(t2, t2), 541 | // Compute x' = m^2 - 2*s. 542 | MMUL(Px, t1, t1), 543 | MSUB(Px, t2), 544 | MSUB(Px, t2), 545 | // Compute z' = 2*y*z. 546 | MMUL(t4, Py, Pz), 547 | MSET(Pz, t4), 548 | MADD(Pz, t4), 549 | // Compute y' = m*(s - x') - 8*y^4. Note that we already have 550 | // 2*y^2 in t3. 551 | MSUB(t2, Px), 552 | MMUL(Py, t1, t2), 553 | MMUL(t4, t3, t3), 554 | MSUB(Py, t4), 555 | MSUB(Py, t4), 556 | ENDCODE, 557 | }; 558 | const _add = [_]u16{ 559 | // Compute u1 = x1*z2^2 (in t1) and s1 = y1*z2^3 (in t3). 560 | MMUL(t3, P2z, P2z), 561 | MMUL(t1, P1x, t3), 562 | MMUL(t4, P2z, t3), 563 | MMUL(t3, P1y, t4), 564 | // Compute u2 = x2*z1^2 (in t2) and s2 = y2*z1^3 (in t4). 565 | MMUL(t4, P1z, P1z), 566 | MMUL(t2, P2x, t4), 567 | MMUL(t5, P1z, t4), 568 | MMUL(t4, P2y, t5), 569 | //Compute h = u2 - u1 (in t2) and r = s2 - s1 (in t4). 570 | MSUB(t2, t1), 571 | MSUB(t4, t3), 572 | // Report cases where r = 0 through the returned flag. 573 | MTZ(t4), 574 | // Compute u1*h^2 (in t6) and h^3 (in t5). 575 | MMUL(t7, t2, t2), 576 | MMUL(t6, t1, t7), 577 | MMUL(t5, t7, t2), 578 | // Compute x3 = r^2 - h^3 - 2*u1*h^2. 579 | // t1 and t7 can be used as scratch registers. 580 | MMUL(P1x, t4, t4), 581 | MSUB(P1x, t5), 582 | MSUB(P1x, t6), 583 | MSUB(P1x, t6), 584 | //Compute y3 = r*(u1*h^2 - x3) - s1*h^3. 585 | MSUB(t6, P1x), 586 | MMUL(P1y, t4, t6), 587 | MMUL(t1, t5, t3), 588 | MSUB(P1y, t1), 589 | //Compute z3 = h*z1*z2. 590 | MMUL(t1, P1z, P2z), 591 | MMUL(P1z, t1, t2), 592 | ENDCODE, 593 | }; 594 | const affine = [_]u16{ 595 | // Save z*R in t1. 596 | MSET(t1, P1z), 597 | // Compute z^3 in t2. 598 | MMUL(t2, P1z, P1z), 599 | MMUL(t3, P1z, t2), 600 | MMUL(t2, t3, P2z), 601 | // Invert to (1/z^3) in t2. 602 | MINV(t2, t3, t4), 603 | // Compute y. 604 | MSET(t3, P1y), 605 | MMUL(P1y, t2, t3), 606 | // Compute (1/z^2) in t3. 607 | MMUL(t3, t2, t1), 608 | // Compute x. 609 | MSET(t2, P1x), 610 | MMUL(P1x, t2, t3), 611 | ENDCODE, 612 | }; 613 | }; 614 | 615 | fn decode_mod( 616 | comptime Curve: type, 617 | x: *[jacobian_len(Curve)]u32, 618 | src: [Curve.point_len / 2]u8, 619 | ) u32 { 620 | const mlen = comptime ((Curve.P[0] + 31) >> 5); 621 | const tlen = comptime std.math.max(mlen << 2, Curve.point_len / 2) + 4; 622 | 623 | var r: u32 = 0; 624 | var pass: usize = 0; 625 | while (pass < 2) : (pass += 1) { 626 | var v: usize = 1; 627 | var acc: u32 = 0; 628 | var acc_len: u32 = 0; 629 | 630 | var u: usize = 0; 631 | while (u < tlen) : (u += 1) { 632 | const b = if (u < Curve.point_len / 2) 633 | @as(u32, src[Curve.point_len / 2 - 1 - u]) 634 | else 635 | 0; 636 | acc |= b << @truncate(u5, acc_len); 637 | acc_len += 8; 638 | if (acc_len >= 31) { 639 | const xw = acc & 0x7FFFFFFF; 640 | acc_len -= 31; 641 | acc = b >> @truncate(u5, 8 - acc_len); 642 | if (v <= mlen) { 643 | if (pass != 0) { 644 | x[v] = r & xw; 645 | } else { 646 | const cc = @bitCast(u32, CMP(xw, Curve.P[v])); 647 | r = MUX(EQ(cc, 0), r, cc); 648 | } 649 | } else if (pass == 0) { 650 | r = MUX(EQ(xw, 0), r, 1); 651 | } 652 | v += 1; 653 | } 654 | } 655 | r >>= 1; 656 | r |= (r << 1); 657 | } 658 | x[0] = Curve.P[0]; 659 | return r & 1; 660 | } 661 | 662 | fn run_code( 663 | comptime Curve: type, 664 | P1: *Jacobian(Curve), 665 | P2: Jacobian(Curve), 666 | comptime Code: []const u16, 667 | ) u32 { 668 | const jaclen = comptime jacobian_len(Curve); 669 | 670 | var t: [13][jaclen]u32 = undefined; 671 | var result: u32 = 1; 672 | 673 | t[0..3].* = P1.*; 674 | t[3..6].* = P2; 675 | 676 | comptime var u: usize = 0; 677 | inline while (true) : (u += 1) { 678 | comptime var op = Code[u]; 679 | if (op == 0) 680 | break; 681 | const d = comptime (op >> 8) & 0x0F; 682 | const a = comptime (op >> 4) & 0x0F; 683 | const b = comptime op & 0x0F; 684 | op >>= 12; 685 | 686 | switch (op) { 687 | 0 => t[d] = t[a], 688 | 1 => { 689 | var ctl = add(&t[d], &t[a], 1); 690 | ctl |= NOT(sub(&t[d], &Curve.P, 0)); 691 | _ = sub(&t[d], &Curve.P, ctl); 692 | }, 693 | 2 => _ = add(&t[d], &Curve.P, sub(&t[d], &t[a], 1)), 694 | 3 => montymul(&t[d], &t[a], &t[b], &Curve.P, 1), 695 | 4 => { 696 | var tp: [Curve.point_len / 2]u8 = undefined; 697 | encode_jacobian_part(&tp, &Curve.P); 698 | tp[Curve.point_len / 2 - 1] -= 2; 699 | modpow(Curve, &t[d], tp, 1, &t[a], &t[b]); 700 | }, 701 | else => result &= ~iszero(&t[d]), 702 | } 703 | } 704 | P1.* = t[0..3].*; 705 | return result; 706 | } 707 | 708 | inline fn MUL31(x: u32, y: u32) u64 { 709 | return @as(u64, x) * @as(u64, y); 710 | } 711 | 712 | inline fn MUL31_lo(x: u32, y: u32) u32 { 713 | return (x *% y) & 0x7FFFFFFF; 714 | } 715 | 716 | inline fn MUX(ctl: u32, x: u32, y: u32) u32 { 717 | return y ^ (@bitCast(u32, -@bitCast(i32, ctl)) & (x ^ y)); 718 | } 719 | inline fn NOT(ctl: u32) u32 { 720 | return ctl ^ 1; 721 | } 722 | inline fn NEQ(x: u32, y: u32) u32 { 723 | const q = x ^ y; 724 | return (q | @bitCast(u32, -@bitCast(i32, q))) >> 31; 725 | } 726 | inline fn EQ(x: u32, y: u32) u32 { 727 | const q = x ^ y; 728 | return NOT((q | @bitCast(u32, -@bitCast(i32, q))) >> 31); 729 | } 730 | inline fn CMP(x: u32, y: u32) i32 { 731 | return @bitCast(i32, GT(x, y)) | -@bitCast(i32, GT(y, x)); 732 | } 733 | inline fn GT(x: u32, y: u32) u32 { 734 | const z = y -% x; 735 | return (z ^ ((x ^ y) & (x ^ z))) >> 31; 736 | } 737 | inline fn LT(x: u32, y: u32) u32 { 738 | return GT(y, x); 739 | } 740 | inline fn GE(x: u32, y: u32) u32 { 741 | return NOT(GT(y, x)); 742 | } 743 | 744 | fn CCOPY(ctl: u32, dst: []u8, src: []const u8) void { 745 | for (src) |s, i| { 746 | dst[i] = @truncate(u8, MUX(ctl, s, dst[i])); 747 | } 748 | } 749 | 750 | inline fn set_zero(out: [*]u32, bit_len: u32) void { 751 | out[0] = bit_len; 752 | mem.set(u32, (out + 1)[0 .. (bit_len + 31) >> 5], 0); 753 | } 754 | 755 | fn divrem(_hi: u32, _lo: u32, d: u32, r: *u32) u32 { 756 | var hi = _hi; 757 | var lo = _lo; 758 | var q: u32 = 0; 759 | const ch = EQ(hi, d); 760 | hi = MUX(ch, 0, hi); 761 | 762 | var k: u5 = 31; 763 | while (k > 0) : (k -= 1) { 764 | const j = @truncate(u5, 32 - @as(u6, k)); 765 | const w = (hi << j) | (lo >> k); 766 | const ctl = GE(w, d) | (hi >> k); 767 | const hi2 = (w -% d) >> j; 768 | const lo2 = lo -% (d << k); 769 | hi = MUX(ctl, hi2, hi); 770 | lo = MUX(ctl, lo2, lo); 771 | q |= ctl << k; 772 | } 773 | const cf = GE(lo, d) | hi; 774 | q |= cf; 775 | r.* = MUX(cf, lo -% d, lo); 776 | return q; 777 | } 778 | 779 | inline fn div(hi: u32, lo: u32, d: u32) u32 { 780 | var r: u32 = undefined; 781 | return divrem(hi, lo, d, &r); 782 | } 783 | 784 | fn muladd_small(x: [*]u32, z: u32, m: [*]const u32) void { 785 | var a0: u32 = undefined; 786 | var a1: u32 = undefined; 787 | var b0: u32 = undefined; 788 | const mblr = @intCast(u5, m[0] & 31); 789 | const mlen = (m[0] + 31) >> 5; 790 | const hi = x[mlen]; 791 | if (mblr == 0) { 792 | a0 = x[mlen]; 793 | mem.copyBackwards(u32, (x + 2)[0 .. mlen - 1], (x + 1)[0 .. mlen - 1]); 794 | x[1] = z; 795 | a1 = x[mlen]; 796 | b0 = m[mlen]; 797 | } else { 798 | a0 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & 0x7FFFFFFF; 799 | mem.copyBackwards(u32, (x + 2)[0 .. mlen - 1], (x + 1)[0 .. mlen - 1]); 800 | x[1] = z; 801 | a1 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & 0x7FFFFFFF; 802 | b0 = ((m[mlen] << (31 - mblr)) | (m[mlen - 1] >> mblr)) & 0x7FFFFFFF; 803 | } 804 | 805 | const g = div(a0 >> 1, a1 | (a0 << 31), b0); 806 | const q = MUX(EQ(a0, b0), 0x7FFFFFFF, MUX(EQ(g, 0), 0, g -% 1)); 807 | 808 | var cc: u32 = 0; 809 | var tb: u32 = 1; 810 | var u: usize = 1; 811 | while (u <= mlen) : (u += 1) { 812 | const mw = m[u]; 813 | const zl = MUL31(mw, q) + cc; 814 | cc = @truncate(u32, zl >> 31); 815 | const zw = @truncate(u32, zl) & 0x7FFFFFFF; 816 | const xw = x[u]; 817 | var nxw = xw -% zw; 818 | cc += nxw >> 31; 819 | nxw &= 0x7FFFFFFF; 820 | x[u] = nxw; 821 | tb = MUX(EQ(nxw, mw), tb, GT(nxw, mw)); 822 | } 823 | 824 | const over = GT(cc, hi); 825 | const under = ~over & (tb | LT(cc, hi)); 826 | _ = add(x, m, over); 827 | _ = sub(x, m, under); 828 | } 829 | 830 | fn to_monty(x: [*]u32, m: [*]const u32) void { 831 | const mlen = (m[0] + 31) >> 5; 832 | var k = mlen; 833 | while (k > 0) : (k -= 1) { 834 | muladd_small(x, 0, m); 835 | } 836 | } 837 | 838 | fn modpow( 839 | comptime Curve: type, 840 | x: *[jacobian_len(Curve)]u32, 841 | e: [Curve.point_len / 2]u8, 842 | m0i: u32, 843 | t1: *[jacobian_len(Curve)]u32, 844 | t2: *[jacobian_len(Curve)]u32, 845 | ) void { 846 | t1.* = x.*; 847 | to_monty(t1, &Curve.P); 848 | set_zero(x, Curve.P[0]); 849 | x[1] = 1; 850 | const bitlen = comptime (Curve.point_len / 2) << 3; 851 | var k: usize = 0; 852 | while (k < bitlen) : (k += 1) { 853 | const ctl = (e[Curve.point_len / 2 - 1 - (k >> 3)] >> (@truncate(u3, k & 7))) & 1; 854 | montymul(t2, x, t1, &Curve.P, m0i); 855 | CCOPY(ctl, mem.asBytes(x), mem.asBytes(t2)); 856 | montymul(t2, t1, t1, &Curve.P, m0i); 857 | t1.* = t2.*; 858 | } 859 | } 860 | 861 | fn encode_jacobian_part(dst: []u8, x: [*]const u32) void { 862 | const xlen = (x[0] + 31) >> 5; 863 | 864 | var buf = @ptrToInt(dst.ptr) + dst.len; 865 | var len: usize = dst.len; 866 | var k: usize = 1; 867 | var acc: u32 = 0; 868 | var acc_len: u5 = 0; 869 | while (len != 0) { 870 | const w = if (k <= xlen) x[k] else 0; 871 | k += 1; 872 | if (acc_len == 0) { 873 | acc = w; 874 | acc_len = 31; 875 | } else { 876 | const z = acc | (w << acc_len); 877 | acc_len -= 1; 878 | acc = w >> (31 - acc_len); 879 | if (len >= 4) { 880 | buf -= 4; 881 | len -= 4; 882 | mem.writeIntBig(u32, @intToPtr([*]u8, buf)[0..4], z); 883 | } else { 884 | switch (len) { 885 | 3 => { 886 | @intToPtr(*u8, buf - 3).* = @truncate(u8, z >> 16); 887 | @intToPtr(*u8, buf - 2).* = @truncate(u8, z >> 8); 888 | }, 889 | 2 => @intToPtr(*u8, buf - 2).* = @truncate(u8, z >> 8), 890 | 1 => {}, 891 | else => unreachable, 892 | } 893 | @intToPtr(*u8, buf - 1).* = @truncate(u8, z); 894 | return; 895 | } 896 | } 897 | } 898 | } 899 | 900 | fn montymul( 901 | out: [*]u32, 902 | x: [*]const u32, 903 | y: [*]const u32, 904 | m: [*]const u32, 905 | m0i: u32, 906 | ) void { 907 | const len = (m[0] + 31) >> 5; 908 | const len4 = len & ~@as(usize, 3); 909 | set_zero(out, m[0]); 910 | var dh: u32 = 0; 911 | var u: usize = 0; 912 | while (u < len) : (u += 1) { 913 | const xu = x[u + 1]; 914 | const f = MUL31_lo(out[1] + MUL31_lo(x[u + 1], y[1]), m0i); 915 | 916 | var r: u64 = 0; 917 | var v: usize = 0; 918 | while (v < len4) : (v += 4) { 919 | comptime var j = 1; 920 | inline while (j <= 4) : (j += 1) { 921 | const z = out[v + j] +% MUL31(xu, y[v + j]) +% MUL31(f, m[v + j]) +% r; 922 | r = z >> 31; 923 | out[v + j - 1] = @truncate(u32, z) & 0x7FFFFFFF; 924 | } 925 | } 926 | while (v < len) : (v += 1) { 927 | const z = out[v + 1] +% MUL31(xu, y[v + 1]) +% MUL31(f, m[v + 1]) +% r; 928 | r = z >> 31; 929 | out[v] = @truncate(u32, z) & 0x7FFFFFFF; 930 | } 931 | dh += @truncate(u32, r); 932 | out[len] = dh & 0x7FFFFFFF; 933 | dh >>= 31; 934 | } 935 | out[0] = m[0]; 936 | const ctl = NEQ(dh, 0) | NOT(sub(out, m, 0)); 937 | _ = sub(out, m, ctl); 938 | } 939 | 940 | fn add(a: [*]u32, b: [*]const u32, ctl: u32) u32 { 941 | var u: usize = 1; 942 | var cc: u32 = 0; 943 | const m = (a[0] + 63) >> 5; 944 | while (u < m) : (u += 1) { 945 | const aw = a[u]; 946 | const bw = b[u]; 947 | const naw = aw +% bw +% cc; 948 | cc = naw >> 31; 949 | a[u] = MUX(ctl, naw & 0x7FFFFFFF, aw); 950 | } 951 | return cc; 952 | } 953 | 954 | fn sub(a: [*]u32, b: [*]const u32, ctl: u32) u32 { 955 | var cc: u32 = 0; 956 | const m = (a[0] + 63) >> 5; 957 | var u: usize = 1; 958 | while (u < m) : (u += 1) { 959 | const aw = a[u]; 960 | const bw = b[u]; 961 | const naw = aw -% bw -% cc; 962 | cc = naw >> 31; 963 | a[u] = MUX(ctl, naw & 0x7FFFFFFF, aw); 964 | } 965 | return cc; 966 | } 967 | 968 | fn iszero(arr: [*]const u32) u32 { 969 | const mlen = (arr[0] + 63) >> 5; 970 | var z: u32 = 0; 971 | var u: usize = mlen - 1; 972 | while (u > 0) : (u -= 1) { 973 | z |= arr[u]; 974 | } 975 | return ~(z | @bitCast(u32, -@bitCast(i32, z))) >> 31; 976 | } 977 | }; 978 | 979 | test "elliptic curve functions with secp384r1 curve" { 980 | { 981 | // Decode to Jacobian then encode again with no operations 982 | var P: ecc.Jacobian(ecc.SECP384R1) = undefined; 983 | _ = ecc.decode_to_jacobian(ecc.SECP384R1, &P, ecc.SECP384R1.base_point); 984 | var out: [96]u8 = undefined; 985 | ecc.encode_from_jacobian(ecc.SECP384R1, &out, P); 986 | try std.testing.expectEqual(ecc.SECP384R1.base_point, out); 987 | 988 | // Multiply by one, check that the result is still the base point 989 | mem.set(u8, &out, 0); 990 | ecc.point_mul(ecc.SECP384R1, &P, &[1]u8{1}); 991 | ecc.encode_from_jacobian(ecc.SECP384R1, &out, P); 992 | try std.testing.expectEqual(ecc.SECP384R1.base_point, out); 993 | } 994 | 995 | { 996 | // @TODO Remove this once std.crypto.rand works in .evented mode 997 | var rand = blk: { 998 | var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined; 999 | try std.os.getrandom(&seed); 1000 | break :blk &std.rand.DefaultCsprng.init(seed).random; 1001 | }; 1002 | 1003 | // Derive a shared secret from a Diffie-Hellman key exchange 1004 | var seed: [48]u8 = undefined; 1005 | rand.bytes(&seed); 1006 | const kp1 = ecc.make_key_pair(ecc.SECP384R1, seed); 1007 | rand.bytes(&seed); 1008 | const kp2 = ecc.make_key_pair(ecc.SECP384R1, seed); 1009 | 1010 | const shared1 = try ecc.scalarmult(ecc.SECP384R1, kp1.public_key, &kp2.secret_key); 1011 | const shared2 = try ecc.scalarmult(ecc.SECP384R1, kp2.public_key, &kp1.secret_key); 1012 | try std.testing.expectEqual(shared1, shared2); 1013 | } 1014 | 1015 | // @TODO Add tests with known points. 1016 | } 1017 | -------------------------------------------------------------------------------- /src/pcks1-1_5.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const Allocator = mem.Allocator; 4 | const Sha224 = std.crypto.hash.sha2.Sha224; 5 | const Sha384 = std.crypto.hash.sha2.Sha384; 6 | const Sha512 = std.crypto.hash.sha2.Sha512; 7 | const Sha256 = std.crypto.hash.sha2.Sha256; 8 | 9 | const x509 = @import("x509.zig"); 10 | const SignatureAlgorithm = x509.Certificate.SignatureAlgorithm; 11 | const asn1 = @import("asn1.zig"); 12 | 13 | fn rsa_perform( 14 | allocator: *Allocator, 15 | modulus: std.math.big.int.Const, 16 | exponent: std.math.big.int.Const, 17 | base: []const u8, 18 | ) !?std.math.big.int.Managed { 19 | // @TODO Better algorithm, make it faster. 20 | const curr_base_limbs = try allocator.alloc( 21 | usize, 22 | std.math.divCeil(usize, base.len, @sizeOf(usize)) catch unreachable, 23 | ); 24 | const curr_base_limb_bytes = @ptrCast([*]u8, curr_base_limbs)[0..base.len]; 25 | mem.copy(u8, curr_base_limb_bytes, base); 26 | mem.reverse(u8, curr_base_limb_bytes); 27 | var curr_base = (std.math.big.int.Mutable{ 28 | .limbs = curr_base_limbs, 29 | .positive = true, 30 | .len = curr_base_limbs.len, 31 | }).toManaged(allocator); 32 | defer curr_base.deinit(); 33 | 34 | var curr_exponent = try exponent.toManaged(allocator); 35 | defer curr_exponent.deinit(); 36 | var result = try std.math.big.int.Managed.initSet(allocator, @as(usize, 1)); 37 | 38 | // encrypted = signature ^ key.exponent MOD key.modulus 39 | while (curr_exponent.toConst().orderAgainstScalar(0) == .gt) { 40 | if (curr_exponent.isOdd()) { 41 | try result.ensureMulCapacity(result.toConst(), curr_base.toConst()); 42 | try result.mul(result.toConst(), curr_base.toConst()); 43 | try llmod(&result, modulus); 44 | } 45 | try curr_base.sqr(curr_base.toConst()); 46 | try llmod(&curr_base, modulus); 47 | try curr_exponent.shiftRight(curr_exponent, 1); 48 | } 49 | 50 | if (result.limbs.len * @sizeOf(usize) < base.len) 51 | return null; 52 | return result; 53 | } 54 | 55 | // res = res mod N 56 | fn llmod(res: *std.math.big.int.Managed, n: std.math.big.int.Const) !void { 57 | var temp = try std.math.big.int.Managed.init(res.allocator); 58 | defer temp.deinit(); 59 | try temp.divTrunc(res, res.toConst(), n); 60 | } 61 | 62 | pub fn algorithm_prefix(signature_algorithm: SignatureAlgorithm) ?[]const u8 { 63 | return switch (signature_algorithm.hash) { 64 | .none, .md5, .sha1 => null, 65 | .sha224 => &[_]u8{ 66 | 0x30, 0x2d, 0x30, 0x0d, 0x06, 67 | 0x09, 0x60, 0x86, 0x48, 0x01, 68 | 0x65, 0x03, 0x04, 0x02, 0x04, 69 | 0x05, 0x00, 0x04, 0x1c, 70 | }, 71 | .sha256 => &[_]u8{ 72 | 0x30, 0x31, 0x30, 0x0d, 0x06, 73 | 0x09, 0x60, 0x86, 0x48, 0x01, 74 | 0x65, 0x03, 0x04, 0x02, 0x01, 75 | 0x05, 0x00, 0x04, 0x20, 76 | }, 77 | .sha384 => &[_]u8{ 78 | 0x30, 0x41, 0x30, 0x0d, 0x06, 79 | 0x09, 0x60, 0x86, 0x48, 0x01, 80 | 0x65, 0x03, 0x04, 0x02, 0x02, 81 | 0x05, 0x00, 0x04, 0x30, 82 | }, 83 | .sha512 => &[_]u8{ 84 | 0x30, 0x51, 0x30, 0x0d, 0x06, 85 | 0x09, 0x60, 0x86, 0x48, 0x01, 86 | 0x65, 0x03, 0x04, 0x02, 0x03, 87 | 0x05, 0x00, 0x04, 0x40, 88 | }, 89 | }; 90 | } 91 | 92 | pub fn sign( 93 | allocator: *Allocator, 94 | signature_algorithm: SignatureAlgorithm, 95 | hash: []const u8, 96 | private_key: x509.PrivateKey, 97 | ) !?[]const u8 { 98 | // @TODO ECDSA signatures 99 | if (signature_algorithm.signature != .rsa or private_key != .rsa) 100 | return null; 101 | 102 | const signature_length = private_key.rsa.modulus.len * @sizeOf(usize); 103 | var sig_buf = try allocator.alloc(u8, signature_length); 104 | defer allocator.free(sig_buf); 105 | const prefix = algorithm_prefix(signature_algorithm) orelse return null; 106 | const first_prefix_idx = sig_buf.len - hash.len - prefix.len; 107 | const first_hash_idx = sig_buf.len - hash.len; 108 | 109 | // EM = 0x00 || 0x01 || PS || 0x00 || T 110 | sig_buf[0] = 0; 111 | sig_buf[1] = 1; 112 | mem.set(u8, sig_buf[2 .. first_prefix_idx - 1], 0xff); 113 | sig_buf[first_prefix_idx - 1] = 0; 114 | mem.copy(u8, sig_buf[first_prefix_idx..first_hash_idx], prefix); 115 | mem.copy(u8, sig_buf[first_hash_idx..], hash); 116 | 117 | const modulus = std.math.big.int.Const{ .limbs = private_key.rsa.modulus, .positive = true }; 118 | const exponent = std.math.big.int.Const{ .limbs = private_key.rsa.exponent, .positive = true }; 119 | 120 | var rsa_result = (try rsa_perform(allocator, modulus, exponent, sig_buf)) orelse return null; 121 | if (rsa_result.limbs.len * @sizeOf(usize) < signature_length) { 122 | rsa_result.deinit(); 123 | return null; 124 | } 125 | 126 | const enc_buf = @ptrCast([*]u8, rsa_result.limbs.ptr)[0..signature_length]; 127 | mem.reverse(u8, enc_buf); 128 | return allocator.resize( 129 | enc_buf.ptr[0 .. rsa_result.limbs.len * @sizeOf(usize)], 130 | signature_length, 131 | ) catch unreachable; 132 | } 133 | 134 | pub fn verify_signature( 135 | allocator: *Allocator, 136 | signature_algorithm: SignatureAlgorithm, 137 | signature: asn1.BitString, 138 | hash: []const u8, 139 | public_key: x509.PublicKey, 140 | ) !bool { 141 | // @TODO ECDSA algorithms 142 | if (public_key != .rsa or signature_algorithm.signature != .rsa) return false; 143 | const prefix = algorithm_prefix(signature_algorithm) orelse return false; 144 | 145 | // RSA hash verification with PKCS 1 V1_5 padding 146 | const modulus = std.math.big.int.Const{ .limbs = public_key.rsa.modulus, .positive = true }; 147 | const exponent = std.math.big.int.Const{ .limbs = public_key.rsa.exponent, .positive = true }; 148 | if (modulus.bitCountAbs() != signature.bit_len) 149 | return false; 150 | 151 | var rsa_result = (try rsa_perform(allocator, modulus, exponent, signature.data)) orelse return false; 152 | defer rsa_result.deinit(); 153 | 154 | if (rsa_result.limbs.len * @sizeOf(usize) < signature.data.len) 155 | return false; 156 | 157 | const enc_buf = @ptrCast([*]u8, rsa_result.limbs.ptr)[0..signature.data.len]; 158 | mem.reverse(u8, enc_buf); 159 | 160 | if (enc_buf[0] != 0x00 or enc_buf[1] != 0x01) 161 | return false; 162 | if (!mem.endsWith(u8, enc_buf, hash)) 163 | return false; 164 | if (!mem.endsWith(u8, enc_buf[0 .. enc_buf.len - hash.len], prefix)) 165 | return false; 166 | if (enc_buf[enc_buf.len - hash.len - prefix.len - 1] != 0x00) 167 | return false; 168 | for (enc_buf[2 .. enc_buf.len - hash.len - prefix.len - 1]) |c| { 169 | if (c != 0xff) return false; 170 | } 171 | 172 | return true; 173 | } 174 | 175 | pub fn certificate_verify_signature( 176 | allocator: *Allocator, 177 | signature_algorithm: x509.Certificate.SignatureAlgorithm, 178 | signature: asn1.BitString, 179 | bytes: []const u8, 180 | public_key: x509.PublicKey, 181 | ) !bool { 182 | // @TODO ECDSA algorithms 183 | if (public_key != .rsa or signature_algorithm.signature != .rsa) return false; 184 | 185 | var hash_buf: [64]u8 = undefined; 186 | var hash: []u8 = undefined; 187 | 188 | switch (signature_algorithm.hash) { 189 | // Deprecated hash algos 190 | .none, .md5, .sha1 => return false, 191 | .sha224 => { 192 | Sha224.hash(bytes, hash_buf[0..28], .{}); 193 | hash = hash_buf[0..28]; 194 | }, 195 | .sha256 => { 196 | Sha256.hash(bytes, hash_buf[0..32], .{}); 197 | hash = hash_buf[0..32]; 198 | }, 199 | .sha384 => { 200 | Sha384.hash(bytes, hash_buf[0..48], .{}); 201 | hash = hash_buf[0..48]; 202 | }, 203 | .sha512 => { 204 | Sha512.hash(bytes, hash_buf[0..64], .{}); 205 | hash = &hash_buf; 206 | }, 207 | } 208 | return try verify_signature(allocator, signature_algorithm, signature, hash, public_key); 209 | } 210 | -------------------------------------------------------------------------------- /src/x509.zig: -------------------------------------------------------------------------------- 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 | pub const CurveId = enum { 11 | sect163k1, sect163r1, sect163r2, sect193r1, 12 | sect193r2, sect233k1, sect233r1, sect239k1, 13 | sect283k1, sect283r1, sect409k1, sect409r1, 14 | sect571k1, sect571r1, secp160k1, secp160r1, 15 | secp160r2, secp192k1, secp192r1, secp224k1, 16 | secp224r1, secp256k1, secp256r1, secp384r1, 17 | secp521r1,brainpoolP256r1, brainpoolP384r1, 18 | brainpoolP512r1, curve25519, curve448, 19 | }; 20 | // zig fmt: on 21 | 22 | pub const PublicKey = union(enum) { 23 | pub const empty = PublicKey{ .ec = .{ .id = undefined, .curve_point = &[0]u8{} } }; 24 | 25 | /// RSA public key 26 | rsa: struct { 27 | //Positive std.math.big.int.Const numbers. 28 | modulus: []const usize, 29 | exponent: []const usize, 30 | }, 31 | /// Elliptic curve public key 32 | ec: struct { 33 | id: CurveId, 34 | /// Public curve point (uncompressed format) 35 | curve_point: []const u8, 36 | }, 37 | 38 | pub fn deinit(self: @This(), alloc: *Allocator) void { 39 | switch (self) { 40 | .rsa => |rsa| { 41 | alloc.free(rsa.modulus); 42 | alloc.free(rsa.exponent); 43 | }, 44 | .ec => |ec| alloc.free(ec.curve_point), 45 | } 46 | } 47 | 48 | pub fn eql(self: @This(), other: @This()) bool { 49 | if (@as(std.meta.Tag(@This()), self) != @as(std.meta.Tag(@This()), other)) 50 | return false; 51 | switch (self) { 52 | .rsa => |mod_exp| return mem.eql(usize, mod_exp.exponent, other.rsa.exponent) and 53 | mem.eql(usize, mod_exp.modulus, other.rsa.modulus), 54 | .ec => |ec| return ec.id == other.ec.id and mem.eql(u8, ec.curve_point, other.ec.curve_point), 55 | } 56 | } 57 | }; 58 | 59 | pub const PrivateKey = PublicKey; 60 | 61 | pub fn parse_public_key(allocator: *Allocator, reader: anytype) !PublicKey { 62 | if ((try reader.readByte()) != 0x30) 63 | return error.MalformedDER; 64 | const seq_len = try asn1.der.parse_length(reader); 65 | _ = seq_len; 66 | 67 | if ((try reader.readByte()) != 0x06) 68 | return error.MalformedDER; 69 | const oid_bytes = try asn1.der.parse_length(reader); 70 | if (oid_bytes == 9) { 71 | // @TODO This fails in async if merged with the if 72 | if (!try reader.isBytes(&[9]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1 })) 73 | return error.MalformedDER; 74 | // OID is 1.2.840.113549.1.1.1 75 | // RSA key 76 | // Skip past the NULL 77 | const null_byte = try reader.readByte(); 78 | if (null_byte != 0x05) 79 | return error.MalformedDER; 80 | const null_len = try asn1.der.parse_length(reader); 81 | if (null_len != 0x00) 82 | return error.MalformedDER; 83 | { 84 | // BitString next! 85 | if ((try reader.readByte()) != 0x03) 86 | return error.MalformedDER; 87 | _ = try asn1.der.parse_length(reader); 88 | const bit_string_unused_bits = try reader.readByte(); 89 | if (bit_string_unused_bits != 0) 90 | return error.MalformedDER; 91 | 92 | if ((try reader.readByte()) != 0x30) 93 | return error.MalformedDER; 94 | _ = try asn1.der.parse_length(reader); 95 | 96 | // Modulus 97 | if ((try reader.readByte()) != 0x02) 98 | return error.MalformedDER; 99 | const modulus = try asn1.der.parse_int(allocator, reader); 100 | errdefer allocator.free(modulus.limbs); 101 | if (!modulus.positive) return error.MalformedDER; 102 | // Exponent 103 | if ((try reader.readByte()) != 0x02) 104 | return error.MalformedDER; 105 | const exponent = try asn1.der.parse_int(allocator, reader); 106 | errdefer allocator.free(exponent.limbs); 107 | if (!exponent.positive) return error.MalformedDER; 108 | return PublicKey{ 109 | .rsa = .{ 110 | .modulus = modulus.limbs, 111 | .exponent = exponent.limbs, 112 | }, 113 | }; 114 | } 115 | } else if (oid_bytes == 7) { 116 | // @TODO This fails in async if merged with the if 117 | if (!try reader.isBytes(&[7]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 })) 118 | return error.MalformedDER; 119 | // OID is 1.2.840.10045.2.1 120 | // Elliptical curve 121 | // We only support named curves, for which the parameter field is an OID. 122 | const oid_tag = try reader.readByte(); 123 | if (oid_tag != 0x06) 124 | return error.MalformedDER; 125 | const curve_oid_bytes = try asn1.der.parse_length(reader); 126 | 127 | var key: PublicKey = undefined; 128 | if (curve_oid_bytes == 5) { 129 | if (!try reader.isBytes(&[4]u8{ 0x2B, 0x81, 0x04, 0x00 })) 130 | return error.MalformedDER; 131 | // 1.3.132.0.{34, 35} 132 | const last_byte = try reader.readByte(); 133 | if (last_byte == 0x22) 134 | key = .{ .ec = .{ .id = .secp384r1, .curve_point = undefined } } 135 | else if (last_byte == 0x23) 136 | key = .{ .ec = .{ .id = .secp521r1, .curve_point = undefined } } 137 | else 138 | return error.MalformedDER; 139 | } else if (curve_oid_bytes == 8) { 140 | if (!try reader.isBytes(&[8]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x3, 0x1, 0x7 })) 141 | return error.MalformedDER; 142 | key = .{ .ec = .{ .id = .secp256r1, .curve_point = undefined } }; 143 | } else { 144 | return error.MalformedDER; 145 | } 146 | 147 | if ((try reader.readByte()) != 0x03) 148 | return error.MalformedDER; 149 | const byte_len = try asn1.der.parse_length(reader); 150 | const unused_bits = try reader.readByte(); 151 | const bit_count = (byte_len - 1) * 8 - unused_bits; 152 | if (bit_count % 8 != 0) 153 | return error.MalformedDER; 154 | const bit_memory = try allocator.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable); 155 | errdefer allocator.free(bit_memory); 156 | try reader.readNoEof(bit_memory[0 .. byte_len - 1]); 157 | 158 | key.ec.curve_point = bit_memory; 159 | return key; 160 | } 161 | return error.MalformedDER; 162 | } 163 | 164 | pub fn DecodeDERError(comptime Reader: type) type { 165 | return Reader.Error || error{ 166 | MalformedPEM, 167 | MalformedDER, 168 | EndOfStream, 169 | OutOfMemory, 170 | }; 171 | } 172 | 173 | pub const Certificate = struct { 174 | pub const SignatureAlgorithm = struct { 175 | hash: enum(u8) { 176 | none = 0, 177 | md5 = 1, 178 | sha1 = 2, 179 | sha224 = 3, 180 | sha256 = 4, 181 | sha384 = 5, 182 | sha512 = 6, 183 | }, 184 | signature: enum(u8) { 185 | anonymous = 0, 186 | rsa = 1, 187 | dsa = 2, 188 | ecdsa = 3, 189 | }, 190 | }; 191 | 192 | /// Subject distinguished name 193 | dn: []const u8, 194 | /// A "CA" anchor is deemed fit to verify signatures on certificates. 195 | /// A "non-CA" anchor is accepted only for direct trust (server's certificate 196 | /// name and key match the anchor). 197 | is_ca: bool = false, 198 | public_key: PublicKey, 199 | 200 | const CaptureState = struct { 201 | self: *Certificate, 202 | allocator: *Allocator, 203 | dn_allocated: bool = false, 204 | pk_allocated: bool = false, 205 | }; 206 | 207 | fn initSubjectDn(state: *CaptureState, tag: u8, length: usize, reader: anytype) !void { 208 | _ = tag; 209 | 210 | const dn_mem = try state.allocator.alloc(u8, length); 211 | errdefer state.allocator.free(dn_mem); 212 | try reader.readNoEof(dn_mem); 213 | state.self.dn = dn_mem; 214 | state.dn_allocated = true; 215 | } 216 | 217 | fn processExtension(state: *CaptureState, tag: u8, length: usize, reader: anytype) !void { 218 | _ = tag; 219 | _ = length; 220 | 221 | const object_id = try asn1.der.parse_value(state.allocator, reader); 222 | defer object_id.deinit(state.allocator); 223 | if (object_id != .object_identifier) return error.DoesNotMatchSchema; 224 | if (object_id.object_identifier.len != 4) 225 | return; 226 | 227 | const data = object_id.object_identifier.data; 228 | // Basic constraints extension 229 | if (data[0] != 2 or data[1] != 5 or data[2] != 29 or data[3] != 19) 230 | return; 231 | 232 | const basic_constraints = try asn1.der.parse_value(state.allocator, reader); 233 | defer basic_constraints.deinit(state.allocator); 234 | 235 | switch (basic_constraints) { 236 | .bool => state.self.is_ca = true, 237 | .octet_string => |s| { 238 | if (s.len != 5 or s[0] != 0x30 or s[1] != 0x03 or s[2] != 0x01 or s[3] != 0x01) 239 | return error.DoesNotMatchSchema; 240 | state.self.is_ca = s[4] != 0x00; 241 | }, 242 | else => return error.DoesNotMatchSchema, 243 | } 244 | } 245 | 246 | fn initExtensions(state: *CaptureState, tag: u8, length: usize, reader: anytype) !void { 247 | _ = tag; 248 | _ = length; 249 | 250 | const schema = .{ 251 | .sequence_of, 252 | .{ .capture, 0, .sequence }, 253 | }; 254 | const captures = .{ 255 | state, processExtension, 256 | }; 257 | try asn1.der.parse_schema(schema, captures, reader); 258 | } 259 | 260 | fn initPublicKeyInfo(state: *CaptureState, tag: u8, length: usize, reader: anytype) !void { 261 | _ = tag; 262 | _ = length; 263 | 264 | state.self.public_key = try parse_public_key(state.allocator, reader); 265 | state.pk_allocated = true; 266 | } 267 | 268 | /// Initialize a trusted anchor from distinguished encoding rules (DER) encoded data 269 | pub fn create(allocator: *Allocator, der_reader: anytype) DecodeDERError(@TypeOf(der_reader))!@This() { 270 | var self: @This() = undefined; 271 | self.is_ca = false; 272 | // https://tools.ietf.org/html/rfc5280#page-117 273 | const schema = .{ 274 | .sequence, .{ 275 | // tbsCertificate 276 | .{ 277 | .sequence, 278 | .{ 279 | .{ .context_specific, 0 }, // version 280 | .{.int}, // serialNumber 281 | .{.sequence}, // signature 282 | .{.sequence}, // issuer 283 | .{.sequence}, // validity, 284 | .{ .capture, 0, .sequence }, // subject 285 | .{ .capture, 1, .sequence }, // subjectPublicKeyInfo 286 | .{ .optional, .context_specific, 1 }, // issuerUniqueID 287 | .{ .optional, .context_specific, 2 }, // subjectUniqueID 288 | .{ .capture, 2, .optional, .context_specific, 3 }, // extensions 289 | }, 290 | }, 291 | // signatureAlgorithm 292 | .{.sequence}, 293 | // signatureValue 294 | .{.bit_string}, 295 | }, 296 | }; 297 | 298 | var capture_state = CaptureState{ 299 | .self = &self, 300 | .allocator = allocator, 301 | }; 302 | const captures = .{ 303 | &capture_state, initSubjectDn, 304 | &capture_state, initPublicKeyInfo, 305 | &capture_state, initExtensions, 306 | }; 307 | 308 | errdefer { 309 | if (capture_state.dn_allocated) 310 | allocator.free(self.dn); 311 | if (capture_state.pk_allocated) 312 | self.public_key.deinit(allocator); 313 | } 314 | 315 | asn1.der.parse_schema(schema, captures, der_reader) catch |err| switch (err) { 316 | error.InvalidLength, 317 | error.InvalidTag, 318 | error.InvalidContainerLength, 319 | error.DoesNotMatchSchema, 320 | => return error.MalformedDER, 321 | else => |e| return e, 322 | }; 323 | return self; 324 | } 325 | 326 | pub fn deinit(self: @This(), alloc: *Allocator) void { 327 | alloc.free(self.dn); 328 | self.public_key.deinit(alloc); 329 | } 330 | 331 | pub fn format(self: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 332 | _ = fmt; 333 | _ = options; 334 | 335 | try writer.print( 336 | \\CERTIFICATE 337 | \\----------- 338 | \\IS CA: {} 339 | \\Subject distinguished name (encoded): 340 | \\{X} 341 | \\Public key: 342 | \\ 343 | , .{ self.is_ca, self.dn }); 344 | 345 | switch (self.public_key) { 346 | .rsa => |mod_exp| { 347 | const modulus = std.math.big.int.Const{ .positive = true, .limbs = mod_exp.modulus }; 348 | const exponent = std.math.big.int.Const{ .positive = true, .limbs = mod_exp.exponent }; 349 | try writer.print( 350 | \\RSA 351 | \\modulus: {} 352 | \\exponent: {} 353 | \\ 354 | , .{ 355 | modulus, 356 | exponent, 357 | }); 358 | }, 359 | .ec => |ec| { 360 | try writer.print( 361 | \\EC (Curve: {}) 362 | \\point: {} 363 | \\ 364 | , .{ 365 | ec.id, 366 | ec.curve_point, 367 | }); 368 | }, 369 | } 370 | 371 | try writer.writeAll( 372 | \\----------- 373 | \\ 374 | ); 375 | } 376 | }; 377 | 378 | pub const CertificateChain = struct { 379 | data: std.ArrayList(Certificate), 380 | 381 | pub fn from_pem(allocator: *Allocator, pem_reader: anytype) DecodeDERError(@TypeOf(pem_reader))!@This() { 382 | var self = @This(){ .data = std.ArrayList(Certificate).init(allocator) }; 383 | errdefer self.deinit(); 384 | 385 | var it = pemCertificateIterator(pem_reader); 386 | while (try it.next()) |cert_reader| { 387 | var buffered = std.io.bufferedReader(cert_reader); 388 | const anchor = try Certificate.create(allocator, buffered.reader()); 389 | errdefer anchor.deinit(allocator); 390 | try self.data.append(anchor); 391 | } 392 | return self; 393 | } 394 | 395 | pub fn deinit(self: @This()) void { 396 | const alloc = self.data.allocator; 397 | for (self.data.items) |ta| ta.deinit(alloc); 398 | self.data.deinit(); 399 | } 400 | }; 401 | 402 | pub fn get_signature_algorithm( 403 | reader: anytype, 404 | ) (@TypeOf(reader).Error || error{EndOfStream})!?Certificate.SignatureAlgorithm { 405 | const oid_tag = try reader.readByte(); 406 | if (oid_tag != 0x06) 407 | return null; 408 | 409 | const oid_length = try asn1.der.parse_length(reader); 410 | if (oid_length == 9) { 411 | var oid_bytes: [9]u8 = undefined; 412 | try reader.readNoEof(&oid_bytes); 413 | 414 | if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 })) { 415 | // TODO: Is hash actually none here? 416 | return Certificate.SignatureAlgorithm{ .signature = .rsa, .hash = .none }; 417 | } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 })) { 418 | return Certificate.SignatureAlgorithm{ .signature = .rsa, .hash = .md5 }; 419 | } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 })) { 420 | return Certificate.SignatureAlgorithm{ .signature = .rsa, .hash = .sha1 }; 421 | } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B })) { 422 | return Certificate.SignatureAlgorithm{ .signature = .rsa, .hash = .sha256 }; 423 | } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C })) { 424 | return Certificate.SignatureAlgorithm{ .signature = .rsa, .hash = .sha384 }; 425 | } else if (mem.eql(u8, &oid_bytes, &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D })) { 426 | return Certificate.SignatureAlgorithm{ .signature = .rsa, .hash = .sha512 }; 427 | } else { 428 | return null; 429 | } 430 | return; 431 | } else if (oid_length == 10) { 432 | // TODO 433 | // ECDSA + algorithms 434 | } 435 | return null; 436 | } 437 | 438 | pub const ClientCertificateChain = struct { 439 | /// Number of certificates in the chain 440 | cert_len: usize, 441 | /// Contains the raw data of each certificate in the certificate chain 442 | raw_certs: [*]const []const u8, 443 | /// Issuer distinguished name in DER format of each certificate in the certificate chain 444 | /// issuer_dn[N] is a dubslice of raw[N] 445 | cert_issuer_dns: [*]const []const u8, 446 | signature_algorithm: Certificate.SignatureAlgorithm, 447 | private_key: PrivateKey, 448 | 449 | // TODO: Encrypted private keys, non-RSA private keys 450 | pub fn from_pem(allocator: *Allocator, pem_reader: anytype) !@This() { 451 | var it = PEMSectionIterator(@TypeOf(pem_reader), .{ 452 | .section_names = &.{ 453 | "X.509 CERTIFICATE", 454 | "CERTIFICATE", 455 | "RSA PRIVATE KEY", 456 | }, 457 | .skip_irrelevant_lines = true, 458 | }){ .reader = pem_reader }; 459 | 460 | var raw_certs = std.ArrayListUnmanaged([]const u8){}; 461 | var cert_issuer_dns = std.ArrayList([]const u8).init(allocator); 462 | errdefer { 463 | for (raw_certs.items) |bytes| { 464 | allocator.free(bytes); 465 | } 466 | raw_certs.deinit(allocator); 467 | cert_issuer_dns.deinit(); 468 | } 469 | 470 | var signature_algorithm: Certificate.SignatureAlgorithm = undefined; 471 | var private_key: ?PrivateKey = null; 472 | errdefer if (private_key) |pk| { 473 | pk.deinit(allocator); 474 | }; 475 | 476 | while (try it.next()) |state_and_reader| { 477 | switch (state_and_reader.state) { 478 | .@"X.509 CERTIFICATE", .@"CERTIFICATE" => { 479 | const cert_bytes = try state_and_reader.reader.readAllAlloc(allocator, std.math.maxInt(usize)); 480 | errdefer allocator.free(cert_bytes); 481 | try raw_certs.append(allocator, cert_bytes); 482 | 483 | const schema = .{ 484 | .sequence, .{ 485 | // tbsCertificate 486 | .{ 487 | .sequence, 488 | .{ 489 | .{ .context_specific, 0 }, // version 490 | .{.int}, // serialNumber 491 | .{.sequence}, // signature 492 | .{ .capture, 0, .sequence }, // issuer 493 | .{.sequence}, // validity 494 | .{.sequence}, // subject 495 | .{.sequence}, // subjectPublicKeyInfo 496 | .{ .optional, .context_specific, 1 }, // issuerUniqueID 497 | .{ .optional, .context_specific, 2 }, // subjectUniqueID 498 | .{ .optional, .context_specific, 3 }, // extensions 499 | }, 500 | }, 501 | // signatureAlgorithm 502 | .{ .capture, 1, .sequence }, 503 | // signatureValue 504 | .{.bit_string}, 505 | }, 506 | }; 507 | 508 | var fbs = std.io.fixedBufferStream(cert_bytes); 509 | const state = .{ 510 | .fbs = &fbs, 511 | .dns = &cert_issuer_dns, 512 | .signature_algorithm = &signature_algorithm, 513 | }; 514 | 515 | const captures = .{ 516 | state, 517 | struct { 518 | fn capture(_state: anytype, tag: u8, length: usize, reader: anytype) !void { 519 | _ = tag; 520 | _ = reader; 521 | 522 | // TODO: Some way to get tag + length buffer directly in the capture callback? 523 | const encoded_length = asn1.der.encode_length(length).slice(); 524 | const pos = _state.fbs.pos; 525 | const dn = _state.fbs.buffer[pos - encoded_length.len - 1 .. pos + length]; 526 | try _state.dns.append(dn); 527 | } 528 | }.capture, 529 | state, 530 | struct { 531 | fn capture(_state: anytype, tag: u8, length: usize, reader: anytype) !void { 532 | _ = tag; 533 | _ = length; 534 | 535 | if (_state.dns.items.len == 1) 536 | _state.signature_algorithm.* = (try get_signature_algorithm(reader)) orelse 537 | return error.InvalidSignatureAlgorithm; 538 | } 539 | }.capture, 540 | }; 541 | 542 | asn1.der.parse_schema(schema, captures, fbs.reader()) catch |err| switch (err) { 543 | error.DoesNotMatchSchema, 544 | error.EndOfStream, 545 | error.InvalidTag, 546 | error.InvalidLength, 547 | error.InvalidSignatureAlgorithm, 548 | error.InvalidContainerLength, 549 | => return error.InvalidCertificate, 550 | error.OutOfMemory => return error.OutOfMemory, 551 | }; 552 | }, 553 | .@"RSA PRIVATE KEY" => { 554 | if (private_key != null) 555 | return error.MultiplePrivateKeys; 556 | 557 | const schema = .{ 558 | .sequence, .{ 559 | .{.int}, // version 560 | .{ .capture, 0, .int }, //modulus 561 | .{.int}, //publicExponent 562 | .{ .capture, 1, .int }, //privateExponent 563 | .{.int}, // prime1 564 | .{.int}, //prime2 565 | .{.int}, //exponent1 566 | .{.int}, //exponent2 567 | .{.int}, //coefficient 568 | .{ .optional, .any }, //otherPrimeInfos 569 | }, 570 | }; 571 | 572 | private_key = .{ .rsa = undefined }; 573 | const state = .{ 574 | .modulus = &private_key.?.rsa.modulus, 575 | .exponent = &private_key.?.rsa.exponent, 576 | .allocator = allocator, 577 | }; 578 | 579 | const captures = .{ 580 | state, 581 | struct { 582 | fn capture(_state: anytype, tag: u8, length: usize, reader: anytype) !void { 583 | _ = tag; 584 | 585 | _state.modulus.* = (try asn1.der.parse_int_with_length( 586 | _state.allocator, 587 | length, 588 | reader, 589 | )).limbs; 590 | } 591 | }.capture, 592 | state, 593 | struct { 594 | fn capture(_state: anytype, tag: u8, length: usize, reader: anytype) !void { 595 | _ = tag; 596 | 597 | _state.exponent.* = (try asn1.der.parse_int_with_length( 598 | _state.allocator, 599 | length, 600 | reader, 601 | )).limbs; 602 | } 603 | }.capture, 604 | }; 605 | 606 | asn1.der.parse_schema(schema, captures, state_and_reader.reader) catch |err| switch (err) { 607 | error.DoesNotMatchSchema, 608 | error.EndOfStream, 609 | error.InvalidTag, 610 | error.InvalidLength, 611 | error.InvalidContainerLength, 612 | => return error.InvalidPrivateKey, 613 | error.OutOfMemory => return error.OutOfMemory, 614 | error.MalformedPEM => return error.MalformedPEM, 615 | }; 616 | }, 617 | .none, .other => unreachable, 618 | } 619 | } 620 | if (private_key == null) 621 | return error.NoPrivateKey; 622 | 623 | std.debug.assert(cert_issuer_dns.items.len == raw_certs.items.len); 624 | return @This(){ 625 | .cert_len = raw_certs.items.len, 626 | .raw_certs = raw_certs.toOwnedSlice(allocator).ptr, 627 | .cert_issuer_dns = cert_issuer_dns.toOwnedSlice().ptr, 628 | .signature_algorithm = signature_algorithm, 629 | .private_key = private_key.?, 630 | }; 631 | } 632 | 633 | pub fn deinit(self: *@This(), allocator: *Allocator) void { 634 | for (self.raw_certs[0..self.cert_len]) |cert_bytes| { 635 | allocator.free(cert_bytes); 636 | } 637 | allocator.free(self.raw_certs[0..self.cert_len]); 638 | allocator.free(self.cert_issuer_dns[0..self.cert_len]); 639 | self.private_key.deinit(allocator); 640 | } 641 | }; 642 | 643 | fn PEMSectionReader(comptime Reader: type, comptime options: PEMSectionIteratorOptions) type { 644 | const Error = Reader.Error || error{MalformedPEM}; 645 | const read = struct { 646 | fn f(it: *PEMSectionIterator(Reader, options), buf: []u8) Error!usize { 647 | var out_idx: usize = 0; 648 | if (it.waiting_chars_len > 0) { 649 | const rest_written = std.math.min(it.waiting_chars_len, buf.len); 650 | while (out_idx < rest_written) : (out_idx += 1) { 651 | buf[out_idx] = it.waiting_chars[out_idx]; 652 | } 653 | 654 | it.waiting_chars_len -= rest_written; 655 | if (it.waiting_chars_len != 0) { 656 | std.mem.copy(u8, it.waiting_chars[0..], it.waiting_chars[rest_written..]); 657 | } 658 | 659 | if (out_idx == buf.len) { 660 | return out_idx; 661 | } 662 | } 663 | if (it.state == .none) 664 | return out_idx; 665 | 666 | var base64_buf: [4]u8 = undefined; 667 | var base64_idx: usize = 0; 668 | while (true) { 669 | const byte = it.reader.readByte() catch |err| switch (err) { 670 | error.EndOfStream => return out_idx, 671 | else => |e| return e, 672 | }; 673 | 674 | if (byte == '-') { 675 | if (it.reader.isBytes("----END ") catch |err| switch (err) { 676 | error.EndOfStream => return error.MalformedPEM, 677 | else => |e| return e, 678 | }) { 679 | try it.reader.skipUntilDelimiterOrEof('\n'); 680 | it.state = .none; 681 | return out_idx; 682 | } else return error.MalformedPEM; 683 | } else if (byte == '\r') { 684 | if ((it.reader.readByte() catch |err| switch (err) { 685 | error.EndOfStream => return error.MalformedPEM, 686 | else => |e| return e, 687 | }) != '\n') 688 | return error.MalformedPEM; 689 | continue; 690 | } else if (byte == '\n') 691 | continue; 692 | 693 | base64_buf[base64_idx] = byte; 694 | base64_idx += 1; 695 | if (base64_idx == base64_buf.len) { 696 | base64_idx = 0; 697 | 698 | const out_len = std.base64.standard_decoder.calcSizeForSlice(&base64_buf) catch 699 | return error.MalformedPEM; 700 | 701 | const rest_chars = if (out_len > buf.len - out_idx) 702 | out_len - (buf.len - out_idx) 703 | else 704 | 0; 705 | const buf_chars = out_len - rest_chars; 706 | 707 | var res_buffer: [3]u8 = undefined; 708 | std.base64.standard_decoder.decode(res_buffer[0..out_len], &base64_buf) catch 709 | return error.MalformedPEM; 710 | 711 | var i: u3 = 0; 712 | while (i < buf_chars) : (i += 1) { 713 | buf[out_idx] = res_buffer[i]; 714 | out_idx += 1; 715 | } 716 | 717 | if (rest_chars > 0) { 718 | mem.copy(u8, &it.waiting_chars, res_buffer[i..]); 719 | it.waiting_chars_len = @intCast(u2, rest_chars); 720 | } 721 | if (out_idx == buf.len) 722 | return out_idx; 723 | } 724 | } 725 | } 726 | }.f; 727 | 728 | return std.io.Reader( 729 | *PEMSectionIterator(Reader, options), 730 | Error, 731 | read, 732 | ); 733 | } 734 | 735 | const PEMSectionIteratorOptions = struct { 736 | section_names: []const []const u8, 737 | skip_irrelevant_lines: bool = false, 738 | }; 739 | 740 | fn PEMSectionIterator(comptime Reader: type, comptime options: PEMSectionIteratorOptions) type { 741 | var biggest_name_len = 0; 742 | 743 | var fields: [options.section_names.len + 2]std.builtin.TypeInfo.EnumField = undefined; 744 | fields[0] = .{ .name = "none", .value = 0 }; 745 | fields[1] = .{ .name = "other", .value = 1 }; 746 | for (fields[2..]) |*field, idx| { 747 | field.name = options.section_names[idx]; 748 | field.value = @as(u8, idx + 2); 749 | if (field.name.len > biggest_name_len) 750 | biggest_name_len = field.name.len; 751 | } 752 | 753 | const StateEnum = @Type(.{ 754 | .Enum = .{ 755 | .layout = .Auto, 756 | .tag_type = u8, 757 | .fields = &fields, 758 | .decls = &.{}, 759 | .is_exhaustive = true, 760 | }, 761 | }); 762 | 763 | const _biggest_name_len = biggest_name_len; 764 | 765 | return struct { 766 | pub const SectionReader = PEMSectionReader(Reader, options); 767 | pub const StateAndName = struct { 768 | state: StateEnum, 769 | reader: SectionReader, 770 | }; 771 | pub const NextError = SectionReader.Error || error{EndOfStream}; 772 | 773 | reader: Reader, 774 | // Internal state for the iterator and the current reader. 775 | state: StateEnum = .none, 776 | waiting_chars: [4]u8 = undefined, 777 | waiting_chars_len: u2 = 0, 778 | 779 | // TODO More verification, this will accept lots of invalid PEM 780 | // TODO Simplify code 781 | pub fn next(self: *@This()) NextError!?StateAndName { 782 | self.waiting_chars_len = 0; 783 | outer_loop: while (true) { 784 | const byte = self.reader.readByte() catch |err| switch (err) { 785 | error.EndOfStream => if (self.state == .none) 786 | return null 787 | else 788 | return error.EndOfStream, 789 | else => |e| return e, 790 | }; 791 | 792 | switch (self.state) { 793 | .none => switch (byte) { 794 | '#' => { 795 | try self.reader.skipUntilDelimiterOrEof('\n'); 796 | continue; 797 | }, 798 | '\r', '\n', ' ', '\t' => continue, 799 | '-' => { 800 | if (try self.reader.isBytes("----BEGIN ")) { 801 | var name_char_idx: usize = 0; 802 | var name_buf: [_biggest_name_len]u8 = undefined; 803 | 804 | while (true) { 805 | const next_byte = try self.reader.readByte(); 806 | switch (next_byte) { 807 | '-' => { 808 | try self.reader.skipUntilDelimiterOrEof('\n'); 809 | const name = name_buf[0..name_char_idx]; 810 | for (options.section_names) |sec_name, idx| { 811 | if (mem.eql(u8, sec_name, name)) { 812 | self.state = @intToEnum(StateEnum, @intCast(u8, idx + 2)); 813 | return StateAndName{ 814 | .reader = .{ .context = self }, 815 | .state = self.state, 816 | }; 817 | } 818 | } 819 | self.state = .other; 820 | continue :outer_loop; 821 | }, 822 | '\n' => return error.MalformedPEM, 823 | else => { 824 | if (name_char_idx == _biggest_name_len) { 825 | try self.reader.skipUntilDelimiterOrEof('\n'); 826 | self.state = .other; 827 | continue :outer_loop; 828 | } 829 | name_buf[name_char_idx] = next_byte; 830 | name_char_idx += 1; 831 | }, 832 | } 833 | } 834 | } else return error.MalformedPEM; 835 | }, 836 | else => { 837 | if (options.skip_irrelevant_lines) { 838 | try self.reader.skipUntilDelimiterOrEof('\n'); 839 | continue; 840 | } else { 841 | return error.MalformedPEM; 842 | } 843 | }, 844 | }, 845 | else => switch (byte) { 846 | '#' => { 847 | try self.reader.skipUntilDelimiterOrEof('\n'); 848 | continue; 849 | }, 850 | '\r', '\n', ' ', '\t' => continue, 851 | '-' => { 852 | if (try self.reader.isBytes("----END ")) { 853 | try self.reader.skipUntilDelimiterOrEof('\n'); 854 | self.state = .none; 855 | continue; 856 | } else return error.MalformedPEM; 857 | }, 858 | // TODO: Make sure the character is base64 859 | else => continue, 860 | }, 861 | } 862 | } 863 | } 864 | }; 865 | } 866 | 867 | fn PEMCertificateIterator(comptime Reader: type) type { 868 | const SectionIterator = PEMSectionIterator(Reader, .{ 869 | .section_names = &.{ "X.509 CERTIFICATE", "CERTIFICATE" }, 870 | }); 871 | 872 | return struct { 873 | pub const SectionReader = SectionIterator.SectionReader; 874 | pub const NextError = SectionReader.Error || error{EndOfStream}; 875 | 876 | section_it: SectionIterator, 877 | 878 | pub fn next(self: *@This()) NextError!?SectionReader { 879 | return ((try self.section_it.next()) orelse return null).reader; 880 | } 881 | }; 882 | } 883 | 884 | /// Iterator of io.Reader that each decode one certificate from the PEM reader. 885 | /// Readers do not have to be fully consumed until end of stream, but they must be 886 | /// read from in order. 887 | /// Iterator.SectionReader is the type of the io.Reader, Iterator.NextError is the error 888 | /// set of the next() function. 889 | pub fn pemCertificateIterator(reader: anytype) PEMCertificateIterator(@TypeOf(reader)) { 890 | return .{ .section_it = .{ .reader = reader } }; 891 | } 892 | 893 | pub const NameElement = struct { 894 | // Encoded OID without tag 895 | oid: asn1.ObjectIdentifier, 896 | // Destination buffer 897 | buf: []u8, 898 | status: enum { 899 | not_found, 900 | found, 901 | errored, 902 | }, 903 | }; 904 | 905 | const github_pem = @embedFile("../test/github.pem"); 906 | const github_der = @embedFile("../test/github.der"); 907 | 908 | fn expected_pem_certificate_chain(bytes: []const u8, certs: []const []const u8) !void { 909 | var fbs = std.io.fixedBufferStream(bytes); 910 | 911 | var it = pemCertificateIterator(fbs.reader()); 912 | var idx: usize = 0; 913 | while (try it.next()) |cert_reader| : (idx += 1) { 914 | const result_bytes = try cert_reader.readAllAlloc(std.testing.allocator, std.math.maxInt(usize)); 915 | defer std.testing.allocator.free(result_bytes); 916 | try std.testing.expectEqualSlices(u8, certs[idx], result_bytes); 917 | } 918 | if (idx != certs.len) { 919 | std.debug.panic("Read {} certificates, wanted {}", .{ idx, certs.len }); 920 | } 921 | try std.testing.expect((try it.next()) == null); 922 | } 923 | 924 | fn expected_pem_certificate(bytes: []const u8, cert_bytes: []const u8) !void { 925 | try expected_pem_certificate_chain(bytes, &[1][]const u8{cert_bytes}); 926 | } 927 | 928 | test "pemCertificateIterator" { 929 | try expected_pem_certificate(github_pem, github_der); 930 | try expected_pem_certificate( 931 | \\-----BEGIN BOGUS----- 932 | \\-----END BOGUS----- 933 | \\ 934 | ++ 935 | github_pem, 936 | github_der, 937 | ); 938 | 939 | try expected_pem_certificate_chain( 940 | github_pem ++ 941 | \\ 942 | \\-----BEGIN BOGUS----- 943 | \\-----END BOGUS----- 944 | \\ 945 | ++ github_pem, 946 | &[2][]const u8{ github_der, github_der }, 947 | ); 948 | 949 | try expected_pem_certificate_chain( 950 | \\-----BEGIN BOGUS----- 951 | \\-----END BOGUS----- 952 | \\ 953 | , 954 | &[0][]const u8{}, 955 | ); 956 | 957 | // Try reading byte by byte from a cert reader 958 | { 959 | var fbs = std.io.fixedBufferStream(github_pem ++ "\n# Some comment\n" ++ github_pem); 960 | var it = pemCertificateIterator(fbs.reader()); 961 | // Read a couple of bytes from the first reader, then skip to the next 962 | { 963 | const first_reader = (try it.next()) orelse return error.NoCertificate; 964 | var first_few: [8]u8 = undefined; 965 | const bytes = try first_reader.readAll(&first_few); 966 | try std.testing.expectEqual(first_few.len, bytes); 967 | try std.testing.expectEqualSlices(u8, github_der[0..bytes], &first_few); 968 | } 969 | 970 | const next_reader = (try it.next()) orelse return error.NoCertificate; 971 | var idx: usize = 0; 972 | while (true) : (idx += 1) { 973 | const byte = next_reader.readByte() catch |err| switch (err) { 974 | error.EndOfStream => break, 975 | else => |e| return e, 976 | }; 977 | if (github_der[idx] != byte) { 978 | std.debug.panic("index {}: expected 0x{X}, found 0x{X}", .{ idx, github_der[idx], byte }); 979 | } 980 | } 981 | try std.testing.expectEqual(github_der.len, idx); 982 | try std.testing.expect((try it.next()) == null); 983 | } 984 | } 985 | 986 | test "CertificateChain" { 987 | var fbs = std.io.fixedBufferStream(github_pem ++ 988 | \\ 989 | \\# Hellenic Academic and Research Institutions RootCA 2011 990 | \\-----BEGIN CERTIFICATE----- 991 | \\MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix 992 | \\RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 993 | \\dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p 994 | \\YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw 995 | \\NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK 996 | \\EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl 997 | \\cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl 998 | \\c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB 999 | \\BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz 1000 | \\dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ 1001 | \\fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns 1002 | \\bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD 1003 | \\75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP 1004 | \\FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV 1005 | \\HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp 1006 | \\5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu 1007 | \\b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA 1008 | \\A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p 1009 | \\6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 1010 | \\TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 1011 | \\dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys 1012 | \\Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI 1013 | \\l7WdmplNsDz4SgCbZN2fOUvRJ9e4 1014 | \\-----END CERTIFICATE----- 1015 | \\ 1016 | \\# ePKI Root Certification Authority 1017 | \\-----BEGIN CERTIFICATE----- 1018 | \\MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe 1019 | \\MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 1020 | \\ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe 1021 | \\Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw 1022 | \\IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL 1023 | \\SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF 1024 | \\AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH 1025 | \\SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh 1026 | \\ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X 1027 | \\DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 1028 | \\TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ 1029 | \\fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA 1030 | \\sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU 1031 | \\WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS 1032 | \\nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH 1033 | \\dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip 1034 | \\NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC 1035 | \\AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF 1036 | \\MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH 1037 | \\ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB 1038 | \\uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl 1039 | \\PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP 1040 | \\JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ 1041 | \\gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 1042 | \\j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 1043 | \\5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB 1044 | \\o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS 1045 | \\/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z 1046 | \\Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE 1047 | \\W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D 1048 | \\hNQ+IIX3Sj0rnP0qCglN6oH4EZw= 1049 | \\-----END CERTIFICATE----- 1050 | ); 1051 | const chain = try CertificateChain.from_pem(std.testing.allocator, fbs.reader()); 1052 | defer chain.deinit(); 1053 | } 1054 | -------------------------------------------------------------------------------- /test/DSTRootCAX3.crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ 3 | MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT 4 | DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow 5 | PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD 6 | Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB 7 | AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O 8 | rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq 9 | OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b 10 | xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw 11 | 7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD 12 | aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV 13 | HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG 14 | SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 15 | ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr 16 | AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz 17 | R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 18 | JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo 19 | Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /test/DigiCertGlobalRootCA.crt.pem: -------------------------------------------------------------------------------- 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----- 23 | -------------------------------------------------------------------------------- /test/DigiCertHighAssuranceEVRootCA.crt.pem: -------------------------------------------------------------------------------- 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----- 24 | -------------------------------------------------------------------------------- /test/badssl.com-client.pem: -------------------------------------------------------------------------------- 1 | Bag Attributes 2 | localKeyID: 41 C3 6C 33 C7 E3 36 DD EA 4A 1F C0 B7 23 B8 E6 9C DC D8 0F 3 | subject=C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Client Certificate 4 | 5 | issuer=C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Client Root Certificate Authority 6 | 7 | -----BEGIN CERTIFICATE----- 8 | MIIEqDCCApCgAwIBAgIUK5Ns4y2CzosB/ZoFlaxjZqoBTIIwDQYJKoZIhvcNAQEL 9 | BQAwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM 10 | DVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkJhZFNTTDExMC8GA1UEAwwoQmFkU1NM 11 | IENsaWVudCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xOTExMjcwMDE5 12 | NTdaFw0yMTExMjYwMDE5NTdaMG8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp 13 | Zm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKDAZCYWRTU0wx 14 | IjAgBgNVBAMMGUJhZFNTTCBDbGllbnQgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3 15 | DQEBAQUAA4IBDwAwggEKAoIBAQDHN18R6x5Oz+u6SOXLoxIscz5GHR6cDcCLgyPa 16 | x2XfXHdJs+h6fTy61WGM+aXEhR2SIwbj5997s34m0MsbvkJrFmn0LHK1fuTLCihE 17 | EmxGdCGZA9xrwxFYAkEjP7D8v7cAWRMipYF/JP7VU7xNUo+QSkZ0sOi9k6bNkABK 18 | L3+yP6PqAzsBoKIN5lN/YRLrppsDmk6nrRDo4R3CD+8JQl9quEoOmL22Pc/qpOjL 19 | 1jgOIFSE5y3gwbzDlfCYoAL5V+by1vu0yJShTTK8oo5wvphcFfEHaQ9w5jFg2htd 20 | q99UER3BKuNDuL+zejqGQZCWb0Xsk8S5WBuX8l3Brrg5giqNAgMBAAGjLTArMAkG 21 | A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgeAMAsGA1UdDwQEAwIF4DANBgkqhkiG 22 | 9w0BAQsFAAOCAgEAZBauLzFSOijkDadcippr9C6laHebb0oRS54xAV70E9k5GxfR 23 | /E2EMuQ8X+miRUMXxKquffcDsSxzo2ac0flw94hDx3B6vJIYvsQx9Lzo95Im0DdT 24 | DkHFXhTlv2kjQwFVnEsWYwyGpHMTjanvNkO7sBP9p1bN1qTE3QAeyMZNKWJk5xPl 25 | U298ERar6tl3Z2Cl8mO6yLhrq4ba6iPGw08SENxzuAJW+n8r0rq7EU+bMg5spgT1 26 | CxExzG8Bb0f98ZXMklpYFogkcuH4OUOFyRodotrotm3iRbuvZNk0Zz7N5n1oLTPl 27 | bGPMwBcqaGXvK62NlaRkwjnbkPM4MYvREM0bbAgZD2GHyANBTso8bdWvhLvmoSjs 28 | FSqJUJp17AZ0x/ELWZd69v2zKW9UdPmw0evyVR19elh/7dmtF6wbewc4N4jxQnTq 29 | IItuhIWKWB9edgJz65uZ9ubQWjXoa+9CuWcV/1KxuKCbLHdZXiboLrKm4S1WmMYW 30 | d0sJm95H9mJzcLyhLF7iX2kK6K9ug1y02YCVXBC9WGZc2x6GMS7lDkXSkJFy3EWh 31 | CmfxkmFGwOgwKt3Jd1pF9ftcSEMhu4WcMgxi9vZr9OdkJLxmk033sVKI/hnkPaHw 32 | g0Y2YBH5v0xmi8sYU7weOcwynkjZARpUltBUQ0pWCF5uJsEB8uE8PPDD3c4= 33 | -----END CERTIFICATE----- 34 | 35 | -----BEGIN RSA PRIVATE KEY----- 36 | MIIEowIBAAKCAQEAxzdfEeseTs/rukjly6MSLHM+Rh0enA3Ai4Mj2sdl31x3SbPo 37 | en08utVhjPmlxIUdkiMG4+ffe7N+JtDLG75CaxZp9CxytX7kywooRBJsRnQhmQPc 38 | a8MRWAJBIz+w/L+3AFkTIqWBfyT+1VO8TVKPkEpGdLDovZOmzZAASi9/sj+j6gM7 39 | AaCiDeZTf2ES66abA5pOp60Q6OEdwg/vCUJfarhKDpi9tj3P6qToy9Y4DiBUhOct 40 | 4MG8w5XwmKAC+Vfm8tb7tMiUoU0yvKKOcL6YXBXxB2kPcOYxYNobXavfVBEdwSrj 41 | Q7i/s3o6hkGQlm9F7JPEuVgbl/Jdwa64OYIqjQIDAQABAoIBAFUQf7fW/YoJnk5c 42 | 8kKRzyDL1Lt7k6Zu+NiZlqXEnutRQF5oQ8yJzXS5yH25296eOJI+AqMuT28ypZtN 43 | bGzcQOAZIgTxNcnp9Sf9nlPyyekLjY0Y6PXaxX0e+VFj0N8bvbiYUGNq6HCyC15r 44 | 8uvRZRvnm04YfEj20zLTWkxTG+OwJ6ZNha1vfq8z7MG5JTsZbP0g7e/LrEb3wI7J 45 | Zu9yHQUzq23HhfhpmLN/0l89YLtOaS8WNq4QvKYgZapw/0G1wWoWW4Y2/UpAxZ9r 46 | cqTBWSpCSCCgyWjiNhPbSJWfe/9J2bcanITLcvCLlPWGAHy1wpo9iBH57y7S+7YS 47 | 3yi7lgECgYEA8lwaRIChc38tmtQCNPtai/7uVDdeJe0uv8Jsg04FTF8KMYcD0V1g 48 | +T7rUPA+rTHwv8uAGLdzl4NW5Qryw18rDY+UivnaZkEdEsnlo3fc8MSQF78dDHCX 49 | nwmHfOmBnBoSbLl+W5ByHkJRHOnX+8qKq9ePNFUMf/hZNYuma9BCFBUCgYEA0m2p 50 | VDn12YdhFUUBIH91aD5cQIsBhkHFU4vqW4zBt6TsJpFciWbrBrTeRzeDou59aIsn 51 | zGBrLMykOY+EwwRku9KTVM4U791Z/NFbH89GqyUaicb4or+BXw5rGF8DmzSsDo0f 52 | ixJ9TVD5DmDi3c9ZQ7ljrtdSxPdA8kOoYPFsApkCgYEA08uZSPQAI6aoe/16UEK4 53 | Rk9qhz47kHlNuVZ27ehoyOzlQ5Lxyy0HacmKaxkILOLPuUxljTQEWAv3DAIdVI7+ 54 | WMN41Fq0eVe9yIWXoNtGwUGFirsA77YVSm5RcN++3GQMZedUfUAl+juKFvJkRS4j 55 | MTkXdGw+mDa3/wsjTGSa2mECgYABO6NCWxSVsbVf6oeXKSgG9FaWCjp4DuqZErjM 56 | 0IZSDSVVFIT2SSQXZffncuvSiJMziZ0yFV6LZKeRrsWYXu44K4Oxe4Oj5Cgi0xc1 57 | mIFRf2YoaIIMchLP+8Wk3ummfyiC7VDB/9m8Gj1bWDX8FrrvKqbq31gcz1YSFVNn 58 | PgLkAQKBgFzG8NdL8os55YcjBcOZMUs5QTKiQSyZM0Abab17k9JaqsU0jQtzeFsY 59 | FTiwh2uh6l4gdO/dGC/P0Vrp7F05NnO7oE4T+ojDzVQMnFpCBeL7x08GfUQkphEG 60 | m0Wqhhi8/24Sy934t5Txgkfoltg8ahkx934WjP6WWRnSAu+cf+vW 61 | -----END RSA PRIVATE KEY----- 62 | -------------------------------------------------------------------------------- /test/github.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexnask/iguanaTLS/0d39a361639ad5469f8e4dcdaea35446bbe54b48/test/github.der -------------------------------------------------------------------------------- /test/github.pem: -------------------------------------------------------------------------------- 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----- 24 | -------------------------------------------------------------------------------- /zig.mod: -------------------------------------------------------------------------------- 1 | id: csbnipaad8n77buaszsnjvlmn6j173fl7pkprsctelswjywe 2 | name: iguanaTLS 3 | main: src/main.zig 4 | license: MIT 5 | description: Minimal, experimental TLS 1.2 implementation in Zig 6 | dependencies: 7 | --------------------------------------------------------------------------------