temp push

This commit is contained in:
Kitteh 2021-06-05 08:55:43 +01:00
parent d4e971906e
commit 55fc4c7260
24 changed files with 6505 additions and 20 deletions

View file

@ -1,10 +1,12 @@
const std = @import("std"); const std = @import("std");
const read = @import("./qtshit/read.zig"); const read = @import("./qtshit/read.zig");
const write = @import("./qtshit/write.zig"); const write = @import("./qtshit/write.zig");
const range = @import("./qtshit/utils/RangeIter.zig").range;
const QVariantType = @import("./qtshit/types/QVariant.zig").QVariant; const QVariantType = @import("./qtshit/types/QVariant.zig").QVariant;
const prettyPrintQVariant = @import("./qtshit/utils/prettyPrintQVariant.zig").prettyPrintQVariant; const prettyPrintQVariant = @import("./qtshit/utils/prettyPrintQVariant.zig").prettyPrintQVariant;
const freeQVariant = @import("./qtshit/utils/free/freeQVariant.zig").freeQVariant; const freeQVariant = @import("./qtshit/utils/free/freeQVariant.zig").freeQVariant;
const QVariantMapToQVariantList = @import("./qtshit/utils/QVariantMapToQVariantList.zig").QVariantMapToQVariantList; const QVariantMapToQVariantList = @import("./qtshit/utils/QVariantMapToQVariantList.zig").QVariantMapToQVariantList;
const tls = @import("./deps/iguanaTLS/src/main.zig");
fn dumpDebug(name: []const u8, list: std.ArrayList(u8)) !void { fn dumpDebug(name: []const u8, list: std.ArrayList(u8)) !void {
std.debug.print("dumpDebug list len {d}\n", .{list.items.len}); std.debug.print("dumpDebug list len {d}\n", .{list.items.len});
@ -22,12 +24,84 @@ pub const Client = struct {
allocator: *std.mem.Allocator, allocator: *std.mem.Allocator,
stream: *std.net.Stream, stream: *std.net.Stream,
pub var tlsAllowed = !true;
pub var tlsConnected = !true;
pub const TLSStream = tls.Client(std.net.Stream.Reader, std.net.Stream.Writer, tls.ciphersuites.all, false);
pub var tlsClient: TLSStream = undefined;
pub fn initTLS(s: *Client) !void {
if (!tlsConnected and tlsAllowed) {
var randBuf: [32]u8 = undefined;
try std.os.getrandom(&randBuf);
var rng = std.rand.DefaultCsprng.init(randBuf);
var rand = blk: {
var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
try std.os.getrandom(&seed);
break :blk &std.rand.DefaultCsprng.init(seed).random;
};
tlsClient = try tls.client_connect(.{
.rand = rand,
.temp_allocator = s.allocator,
.reader = s.stream.reader(),
.writer = s.stream.writer(),
.cert_verifier = .none,
.ciphersuites = tls.ciphersuites.all,
}, "quassel.owo.monster");
}
}
pub fn _writeFrame(s: *Client, writer: anytype, data: std.ArrayList(u8)) !void {
try write.writeUInt(writer, @intCast(u32, data.items.len));
try writer.writeAll(data.items);
}
pub fn writeFrame(s: *Client, data: std.ArrayList(u8)) !void {
try s.initTLS();
if (tlsConnected) {
var writer = tlsClient.writer();
try s._writeFrame(writer, data);
} else {
var writer = s.stream.writer();
try s._writeFrame(writer, data);
}
}
pub fn _readFrame(s: *Client, reader: anytype) !std.ArrayList(u8) {
var size = try read.readUInt(reader);
var data = std.ArrayList(u8).init(s.allocator);
var iter = range(u32, 0, size);
while (iter.next()) |i| {
const byte = try reader.readByte();
try data.append(byte);
}
return data;
}
pub fn readFrame(s: *Client) !QVariantType {
try s.initTLS();
var data: std.ArrayList(u8) = undefined;
if (tlsConnected) {
var reader = tlsClient.reader();
data = try s._readFrame(reader);
} else {
var reader = s.stream.reader();
data = try s._readFrame(reader);
}
var fBS = std.io.fixedBufferStream(data.items);
return try read.readQVariant(fBS.reader(), s.allocator);
}
pub fn handshake(s: *Client) !void { pub fn handshake(s: *Client) !void {
//const magic = 0x42b33f00; //const magic = 0x42b33f00;
//try write.writeUInt(s.stream.writer(), magic); //try write.writeUInt(s.stream.writer(), magic);
//try write.writeUInt(s.stream.writer(), 0x80000002); //try write.writeUInt(s.stream.writer(), 0x80000002);
try s.stream.writer().writeAll(&[_]u8{ 0x42, 0xb3, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00 }); try s.stream.writer().writeAll(&[_]u8{ 0x42, 0xb3, 0x3f, 0x01, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00 });
var flags = try read.readByte(s.stream.reader()); var flags = try read.readByte(s.stream.reader());
var extra = try read.readShort(s.stream.reader()); var extra = try read.readShort(s.stream.reader());
@ -36,13 +110,12 @@ pub const Client = struct {
std.debug.print("Handshake: flags={d} extra={d} version={d} \n", .{ flags, extra, version }); std.debug.print("Handshake: flags={d} extra={d} version={d} \n", .{ flags, extra, version });
} }
pub fn quassel_init_packet(s: *Client) !void { pub fn quassel_init_packet(s: *Client) !void {
var list = std.ArrayList(u8).init(s.allocator); var data = std.ArrayList(u8).init(s.allocator);
var map = std.StringHashMap(QVariantType).init(s.allocator); var map = std.StringHashMap(QVariantType).init(s.allocator);
try map.put("MsgType", .{ .String = "ClientInit" }); try map.put("MsgType", .{ .String = "ClientInit" });
try map.put("UseCompression", .{ .UInt = 0 }); try map.put("UseCompression", .{ .UInt = 0 });
try map.put("UseSSL", .{ .UInt = 0 }); try map.put("UseSsl", .{ .UInt = 1 });
try map.put("ProtocolVersion", .{ .UInt = 10 }); try map.put("ProtocolVersion", .{ .UInt = 10 });
try map.put("ClientVersion", .{ .String = "0.1 (quasselclient)" }); try map.put("ClientVersion", .{ .String = "0.1 (quasselclient)" });
@ -57,36 +130,36 @@ pub const Client = struct {
try map.put("FeatureList", .{ .QStringList = featureList.items }); try map.put("FeatureList", .{ .QStringList = featureList.items });
try write.writeFrame(list.writer(), s.allocator, map); try write.writeQVariant(data.writer(), s.allocator, .{
try dumpDebug("ClientInit.bin", list); .QVariantMap = map,
try s.stream.writer().writeAll(list.items); });
//std.debug.print("\n\nInitPacket: \n", .{}); try s.writeFrame(data);
//prettyPrintQVariant(.{ .QVariantMap = map }, 0); var varient = try s.readFrame();
tlsAllowed = varient.QVariantMap.get("SupportSsl").?.Byte == 1;
try s.read_quassel_packet(); prettyPrintQVariant(varient, 0);
freeQVariant(varient, s.allocator);
} }
pub fn quassel_login(s: *Client, username: []const u8, password: []const u8) !void { pub fn quassel_login(s: *Client, username: []const u8, password: []const u8) !void {
var list = std.ArrayList(u8).init(s.allocator); var data = std.ArrayList(u8).init(s.allocator);
var map = std.StringHashMap(QVariantType).init(s.allocator); var map = std.StringHashMap(QVariantType).init(s.allocator);
try map.put("MsgType", .{ .String = "ClientLogin" }); try map.put("MsgType", .{ .String = "ClientLogin" });
try map.put("User", .{ .String = username }); try map.put("User", .{ .String = username });
try map.put("Password", .{ .String = password }); try map.put("Password", .{ .String = password });
try write.writeFrame(list.writer(), s.allocator, map); try write.writeQVariant(data.writer(), s.allocator, .{
try dumpDebug("ClientLogin.bin", list); .QVariantMap = map,
});
try s.stream.writer().writeAll(list.items); try s.writeFrame(data);
} }
pub fn read_quassel_packet(s: *Client) !void { pub fn read_quassel_packet(s: *Client) !void {
var size = read.readUInt(s.stream.reader());
std.debug.print("\n\nQuassel Packet: \n", .{}); std.debug.print("\n\nQuassel Packet: \n", .{});
var varient = try read.readQVariant(s.stream.reader(), s.allocator); var varient = try s.readFrame();
prettyPrintQVariant(varient, 0); prettyPrintQVariant(varient, 0);
freeQVariant(varient, s.allocator); freeQVariant(varient, s.allocator);
} }

1
src/deps/iguanaTLS/.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
*.zig text=auto eol=lf

3
src/deps/iguanaTLS/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/zig-cache
deps.zig
gyro.lock

0
src/deps/iguanaTLS/.gitmodules vendored Normal file
View file

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Alexandros Naskos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,425 @@
const std = @import("std");
const tls = @import("tls");
const use_gpa = @import("build_options").use_gpa;
pub const log_level = .debug;
const RecordingAllocator = struct {
const Stats = struct {
peak_allocated: usize = 0,
total_allocated: usize = 0,
total_deallocated: usize = 0,
total_allocations: usize = 0,
};
allocator: std.mem.Allocator = .{
.allocFn = allocFn,
.resizeFn = resizeFn,
},
base_allocator: *std.mem.Allocator,
stats: Stats = .{},
fn allocFn(
a: *std.mem.Allocator,
len: usize,
ptr_align: u29,
len_align: u29,
ret_addr: usize,
) ![]u8 {
const self = @fieldParentPtr(RecordingAllocator, "allocator", a);
const mem = try self.base_allocator.allocFn(
self.base_allocator,
len,
ptr_align,
len_align,
ret_addr,
);
self.stats.total_allocations += 1;
self.stats.total_allocated += mem.len;
self.stats.peak_allocated = std.math.max(
self.stats.peak_allocated,
self.stats.total_allocated - self.stats.total_deallocated,
);
return mem;
}
fn resizeFn(a: *std.mem.Allocator, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) !usize {
const self = @fieldParentPtr(RecordingAllocator, "allocator", a);
const actual_len = try self.base_allocator.resizeFn(
self.base_allocator,
buf,
buf_align,
new_len,
len_align,
ret_addr,
);
if (actual_len == 0) {
std.debug.assert(new_len == 0);
self.stats.total_deallocated += buf.len;
} else if (actual_len > buf.len) {
self.stats.total_allocated += actual_len - buf.len;
self.stats.peak_allocated = std.math.max(
self.stats.peak_allocated,
self.stats.total_allocated - self.stats.total_deallocated,
);
} else {
self.stats.total_deallocated += buf.len - actual_len;
}
return actual_len;
}
};
const SinkWriter = blk: {
const S = struct {};
break :blk std.io.Writer(S, error{}, struct {
fn f(_: S, buffer: []const u8) !usize {
return buffer.len;
}
}.f);
};
const ReplayingReaderState = struct {
data: []const u8,
};
const ReplayingReader = std.io.Reader(*ReplayingReaderState, error{}, struct {
fn f(self: *ReplayingReaderState, buffer: []u8) !usize {
if (self.data.len < buffer.len)
@panic("Not enoguh reader data!");
std.mem.copy(u8, buffer, self.data[0..buffer.len]);
self.data = self.data[buffer.len..];
return buffer.len;
}
}.f);
const ReplayingRandom = struct {
rand: std.rand.Random = .{ .fillFn = fillFn },
data: []const u8,
fn fillFn(r: *std.rand.Random, buf: []u8) void {
const self = @fieldParentPtr(ReplayingRandom, "rand", r);
if (self.data.len < buf.len)
@panic("Not enough random data!");
std.mem.copy(u8, buf, self.data[0..buf.len]);
self.data = self.data[buf.len..];
}
};
fn benchmark_run(
comptime ciphersuites: anytype,
comptime curves: anytype,
gpa: *std.mem.Allocator,
allocator: *std.mem.Allocator,
running_time: f32,
hostname: []const u8,
port: u16,
trust_anchors: tls.x509.TrustAnchorChain,
reader_recording: []const u8,
random_recording: []const u8,
) !void {
{
const warmup_time_secs = std.math.max(0.5, running_time / 20);
std.debug.print("Warming up for {d:.2} seconds...\n", .{warmup_time_secs});
const warmup_time_ns = @floatToInt(i128, warmup_time_secs * std.time.ns_per_s);
var warmup_time_passed: i128 = 0;
var timer = try std.time.Timer.start();
while (warmup_time_passed < warmup_time_ns) {
var rand = ReplayingRandom{
.data = random_recording,
};
var reader_state = ReplayingReaderState{
.data = reader_recording,
};
const reader = ReplayingReader{ .context = &reader_state };
const writer = SinkWriter{ .context = .{} };
timer.reset();
_ = try tls.client_connect(.{
.rand = &rand.rand,
.reader = reader,
.writer = writer,
.ciphersuites = ciphersuites,
.curves = curves,
.cert_verifier = .default,
.temp_allocator = allocator,
.trusted_certificates = trust_anchors.data.items,
}, hostname);
warmup_time_passed += timer.read();
}
}
{
std.debug.print("Benchmarking for {d:.2} seconds...\n", .{running_time});
const RunRecording = struct {
time: i128,
mem_stats: RecordingAllocator.Stats,
};
var run_recordings = std.ArrayList(RunRecording).init(gpa);
defer run_recordings.deinit();
const bench_time_ns = @floatToInt(i128, running_time * std.time.ns_per_s);
var total_time_passed: i128 = 0;
var iterations: usize = 0;
var timer = try std.time.Timer.start();
while (total_time_passed < bench_time_ns) : (iterations += 1) {
var rand = ReplayingRandom{
.data = random_recording,
};
var reader_state = ReplayingReaderState{
.data = reader_recording,
};
const reader = ReplayingReader{ .context = &reader_state };
const writer = SinkWriter{ .context = .{} };
var recording_allocator = RecordingAllocator{ .base_allocator = allocator };
timer.reset();
_ = try tls.client_connect(.{
.rand = &rand.rand,
.reader = reader,
.writer = writer,
.ciphersuites = ciphersuites,
.curves = curves,
.cert_verifier = .default,
.temp_allocator = &recording_allocator.allocator,
.trusted_certificates = trust_anchors.data.items,
}, hostname);
const runtime = timer.read();
total_time_passed += runtime;
(try run_recordings.addOne()).* = .{
.mem_stats = recording_allocator.stats,
.time = runtime,
};
}
const total_time_secs = @intToFloat(f64, total_time_passed) / std.time.ns_per_s;
const mean_time_ns = @divTrunc(total_time_passed, iterations);
const mean_time_ms = @intToFloat(f64, mean_time_ns) * std.time.ms_per_s / std.time.ns_per_s;
const std_dev_ns = blk: {
var acc: i128 = 0;
for (run_recordings.items) |rec| {
const dt = rec.time - mean_time_ns;
acc += dt * dt;
}
break :blk std.math.sqrt(@divTrunc(acc, iterations));
};
const std_dev_ms = @intToFloat(f64, std_dev_ns) * std.time.ms_per_s / std.time.ns_per_s;
std.debug.print(
\\Finished benchmarking.
\\Total runtime: {d:.2} sec
\\Iterations: {} ({d:.2} iterations/sec)
\\Mean iteration time: {d:.2} ms
\\Standard deviation: {d:.2} ms
\\
, .{
total_time_secs,
iterations,
@intToFloat(f64, iterations) / total_time_secs,
mean_time_ms,
std_dev_ms,
});
// (percentile/100) * (total number n + 1)
std.sort.sort(RunRecording, run_recordings.items, {}, struct {
fn f(_: void, lhs: RunRecording, rhs: RunRecording) bool {
return lhs.time < rhs.time;
}
}.f);
const percentiles = .{ 99.0, 90.0, 75.0, 50.0 };
inline for (percentiles) |percentile| {
if (percentile < iterations) {
const idx = @floatToInt(usize, @intToFloat(f64, iterations + 1) * percentile / 100.0);
std.debug.print(
"{d:.0}th percentile value: {d:.2} ms\n",
.{
percentile,
@intToFloat(f64, run_recordings.items[idx].time) * std.time.ms_per_s / std.time.ns_per_s,
},
);
}
}
const first_mem_stats = run_recordings.items[0].mem_stats;
for (run_recordings.items[1..]) |rec| {
std.debug.assert(std.meta.eql(first_mem_stats, rec.mem_stats));
}
std.debug.print(
\\Peak allocated memory: {Bi:.2},
\\Total allocated memory: {Bi:.2},
\\Number of allocations: {d},
\\
, .{
first_mem_stats.peak_allocated,
first_mem_stats.total_allocated,
first_mem_stats.total_allocations,
});
}
}
fn benchmark_run_with_ciphersuite(
comptime ciphersuites: anytype,
curve_str: []const u8,
gpa: *std.mem.Allocator,
allocator: *std.mem.Allocator,
running_time: f32,
hostname: []const u8,
port: u16,
trust_anchors: tls.x509.TrustAnchorChain,
reader_recording: []const u8,
random_recording: []const u8,
) !void {
if (std.mem.eql(u8, curve_str, "all")) {
return try benchmark_run(
ciphersuites,
tls.curves.all,
gpa,
allocator,
running_time,
hostname,
port,
trust_anchors,
reader_recording,
random_recording,
);
}
inline for (tls.curves.all) |curve| {
if (std.mem.eql(u8, curve_str, curve.name)) {
return try benchmark_run(
ciphersuites,
.{curve},
gpa,
allocator,
running_time,
hostname,
port,
trust_anchors,
reader_recording,
random_recording,
);
}
}
return error.InvalidCurve;
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = &gpa.allocator;
var args = std.process.args();
std.debug.assert(args.skip());
const running_time = blk: {
const maybe_arg = args.next(allocator) orelse return error.NoArguments;
const arg = try maybe_arg;
break :blk std.fmt.parseFloat(f32, arg) catch {
std.log.crit("Running time is not a floating point number...", .{});
return error.InvalidArg;
};
};
// Loop over all files, swap gpa with a fixed buffer allocator for the handhsake
arg_loop: while (args.next(allocator)) |recorded_file_path_or_err| {
const recorded_file_path = try recorded_file_path_or_err;
defer allocator.free(recorded_file_path);
std.debug.print(
\\============================================================
\\{s}
\\============================================================
\\
, .{std.fs.path.basename(recorded_file_path)});
const recorded_file = try std.fs.cwd().openFile(recorded_file_path, .{});
defer recorded_file.close();
const ciphersuite_str_len = try recorded_file.reader().readByte();
const ciphersuite_str = try allocator.alloc(u8, ciphersuite_str_len);
defer allocator.free(ciphersuite_str);
try recorded_file.reader().readNoEof(ciphersuite_str);
const curve_str_len = try recorded_file.reader().readByte();
const curve_str = try allocator.alloc(u8, curve_str_len);
defer allocator.free(curve_str);
try recorded_file.reader().readNoEof(curve_str);
const hostname_len = try recorded_file.reader().readIntLittle(usize);
const hostname = try allocator.alloc(u8, hostname_len);
defer allocator.free(hostname);
try recorded_file.reader().readNoEof(hostname);
const port = try recorded_file.reader().readIntLittle(u16);
const trust_anchors = blk: {
const pem_file_path_len = try recorded_file.reader().readIntLittle(usize);
const pem_file_path = try allocator.alloc(u8, pem_file_path_len);
defer allocator.free(pem_file_path);
try recorded_file.reader().readNoEof(pem_file_path);
const pem_file = try std.fs.cwd().openFile(pem_file_path, .{});
defer pem_file.close();
const tas = try tls.x509.TrustAnchorChain.from_pem(allocator, pem_file.reader());
std.debug.print("Read {} certificates.\n", .{tas.data.items.len});
break :blk tas;
};
defer trust_anchors.deinit();
const reader_recording_len = try recorded_file.reader().readIntLittle(usize);
const reader_recording = try allocator.alloc(u8, reader_recording_len);
defer allocator.free(reader_recording);
try recorded_file.reader().readNoEof(reader_recording);
const random_recording_len = try recorded_file.reader().readIntLittle(usize);
const random_recording = try allocator.alloc(u8, random_recording_len);
defer allocator.free(random_recording);
try recorded_file.reader().readNoEof(random_recording);
const handshake_allocator = if (use_gpa)
&gpa.allocator
else
&std.heap.ArenaAllocator.init(std.heap.page_allocator).allocator;
defer if (!use_gpa)
@fieldParentPtr(std.heap.ArenaAllocator, "allocator", handshake_allocator).deinit();
if (std.mem.eql(u8, ciphersuite_str, "all")) {
try benchmark_run_with_ciphersuite(
tls.ciphersuites.all,
curve_str,
allocator,
handshake_allocator,
running_time,
hostname,
port,
trust_anchors,
reader_recording,
random_recording,
);
continue :arg_loop;
}
inline for (tls.ciphersuites.all) |ciphersuite| {
if (std.mem.eql(u8, ciphersuite_str, ciphersuite.name)) {
try benchmark_run_with_ciphersuite(
.{ciphersuite},
curve_str,
allocator,
handshake_allocator,
running_time,
hostname,
port,
trust_anchors,
reader_recording,
random_recording,
);
continue :arg_loop;
}
}
return error.InvalidCiphersuite;
}
}

View file

@ -0,0 +1,33 @@
const Builder = @import("std").build.Builder;
pub fn build(b: *Builder) void {
const record_build = b.addExecutable("record_handshake", "record_handshake.zig");
record_build.addPackagePath("tls", "../src/main.zig");
record_build.setBuildMode(.Debug);
record_build.install();
const use_gpa = b.option(
bool,
"use-gpa",
"Use the general purpose allocator instead of an arena allocator",
) orelse false;
const bench_build = b.addExecutable("bench", "bench.zig");
bench_build.addPackagePath("tls", "../src/main.zig");
bench_build.setBuildMode(.ReleaseFast);
bench_build.addBuildOption(bool, "use_gpa", use_gpa);
bench_build.install();
const record_run_cmd = record_build.run();
const bench_run_cmd = bench_build.run();
record_run_cmd.step.dependOn(b.getInstallStep());
bench_run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
record_run_cmd.addArgs(args);
bench_run_cmd.addArgs(args);
}
const record_run_step = b.step("record-handshake", "Record a TLS handshake");
const bench_run_step = b.step("bench", "Run the benchmark");
record_run_step.dependOn(&record_run_cmd.step);
bench_run_step.dependOn(&bench_run_cmd.step);
}

View file

@ -0,0 +1,249 @@
const std = @import("std");
const tls = @import("tls");
const RecordingRandom = struct {
rand: std.rand.Random = .{
.fillFn = fillFn,
},
base: *std.rand.Random,
recorded: std.ArrayList(u8),
fn fillFn(r: *std.rand.Random, buf: []u8) void {
const self = @fieldParentPtr(@This(), "rand", r);
self.base.bytes(buf);
self.recorded.writer().writeAll(buf) catch unreachable;
}
};
fn RecordingReaderState(comptime Base: type) type {
return struct {
base: Base,
recorded: std.ArrayList(u8),
fn read(self: *@This(), buffer: []u8) !usize {
var read_bytes = try self.base.read(buffer);
if (read_bytes != 0) {
try self.recorded.writer().writeAll(buffer[0..read_bytes]);
}
return read_bytes;
}
};
}
fn RecordingReader(comptime Base: type) type {
return std.io.Reader(
*RecordingReaderState(Base),
Base.Error || error{OutOfMemory},
RecordingReaderState(Base).read,
);
}
fn record_handshake(
comptime ciphersuites: anytype,
comptime curves: anytype,
allocator: *std.mem.Allocator,
out_name: []const u8,
hostname: []const u8,
port: u16,
pem_file_path: []const u8,
) !void {
// Read PEM file
const pem_file = try std.fs.cwd().openFile(pem_file_path, .{});
defer pem_file.close();
const trust_anchors = try tls.x509.TrustAnchorChain.from_pem(allocator, pem_file.reader());
defer trust_anchors.deinit();
std.log.info("Read {} certificates.", .{trust_anchors.data.items.len});
const sock = try std.net.tcpConnectToHost(allocator, hostname, port);
defer sock.close();
var recording_reader_state = RecordingReaderState(@TypeOf(sock).Reader){
.base = sock.reader(),
.recorded = std.ArrayList(u8).init(allocator),
};
defer recording_reader_state.recorded.deinit();
var recording_random = RecordingRandom{
.base = std.crypto.random,
.recorded = std.ArrayList(u8).init(allocator),
};
defer recording_random.recorded.deinit();
const reader = RecordingReader(@TypeOf(sock).Reader){
.context = &recording_reader_state,
};
std.log.info("Recording session `{s}`...", .{out_name});
var client = try tls.client_connect(.{
.rand = &recording_random.rand,
.reader = reader,
.writer = sock.writer(),
.ciphersuites = ciphersuites,
.curves = curves,
.cert_verifier = .default,
.temp_allocator = allocator,
.trusted_certificates = trust_anchors.data.items,
}, hostname);
defer client.close_notify() catch {};
const out_file = try std.fs.cwd().createFile(out_name, .{});
defer out_file.close();
if (ciphersuites.len > 1) {
try out_file.writeAll(&[_]u8{ 0x3, 'a', 'l', 'l' });
} else {
try out_file.writer().writeIntLittle(u8, ciphersuites[0].name.len);
try out_file.writeAll(ciphersuites[0].name);
}
if (curves.len > 1) {
try out_file.writeAll(&[_]u8{ 0x3, 'a', 'l', 'l' });
} else {
try out_file.writer().writeIntLittle(u8, curves[0].name.len);
try out_file.writeAll(curves[0].name);
}
try out_file.writer().writeIntLittle(usize, hostname.len);
try out_file.writeAll(hostname);
try out_file.writer().writeIntLittle(u16, port);
try out_file.writer().writeIntLittle(usize, pem_file_path.len);
try out_file.writeAll(pem_file_path);
try out_file.writer().writeIntLittle(usize, recording_reader_state.recorded.items.len);
try out_file.writeAll(recording_reader_state.recorded.items);
try out_file.writer().writeIntLittle(usize, recording_random.recorded.items.len);
try out_file.writeAll(recording_random.recorded.items);
std.log.info("Session recorded.\n", .{});
}
fn record_with_ciphersuite(
comptime ciphersuites: anytype,
allocator: *std.mem.Allocator,
out_name: []const u8,
curve_str: []const u8,
hostname: []const u8,
port: u16,
pem_file_path: []const u8,
) !void {
if (std.mem.eql(u8, curve_str, "all")) {
return try record_handshake(
ciphersuites,
tls.curves.all,
allocator,
out_name,
hostname,
port,
pem_file_path,
);
}
inline for (tls.curves.all) |curve| {
if (std.mem.eql(u8, curve_str, curve.name)) {
return try record_handshake(
ciphersuites,
.{curve},
allocator,
out_name,
hostname,
port,
pem_file_path,
);
}
}
std.log.crit("Invalid curve `{s}`", .{curve_str});
std.debug.warn("Available options:\n- all\n", .{});
inline for (tls.curves.all) |curve| {
std.debug.warn("- {s}\n", .{curve.name});
}
return error.InvalidArg;
}
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
pub fn main() !void {
const allocator = &gpa.allocator;
var args = std.process.args();
std.debug.assert(args.skip());
const pem_file_path = try (args.next(allocator) orelse {
std.log.crit("Need PEM file path as first argument", .{});
return error.NotEnoughArgs;
});
defer allocator.free(pem_file_path);
const ciphersuite_str = try (args.next(allocator) orelse {
std.log.crit("Need ciphersuite as second argument", .{});
return error.NotEnoughArgs;
});
defer allocator.free(ciphersuite_str);
const curve_str = try (args.next(allocator) orelse {
std.log.crit("Need curve as third argument", .{});
return error.NotEnoughArgs;
});
defer allocator.free(curve_str);
const hostname_port = try (args.next(allocator) orelse {
std.log.crit("Need hostname:port as fourth argument", .{});
return error.NotEnoughArgs;
});
defer allocator.free(hostname_port);
if (args.skip()) {
std.log.crit("Need exactly four arguments", .{});
return error.TooManyArgs;
}
var hostname_parts = std.mem.split(hostname_port, ":");
const hostname = hostname_parts.next().?;
const port = std.fmt.parseUnsigned(
u16,
hostname_parts.next() orelse {
std.log.crit("Hostname and port should be in `hostname:port` format", .{});
return error.InvalidArg;
},
10,
) catch {
std.log.crit("Port is not a base 10 unsigned integer...", .{});
return error.InvalidArg;
};
if (hostname_parts.next() != null) {
std.log.crit("Hostname and port should be in `hostname:port` format", .{});
return error.InvalidArg;
}
const out_name = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}-{}.handshake", .{
hostname,
ciphersuite_str,
curve_str,
std.time.timestamp(),
});
defer allocator.free(out_name);
if (std.mem.eql(u8, ciphersuite_str, "all")) {
return try record_with_ciphersuite(
tls.ciphersuites.all,
allocator,
out_name,
curve_str,
hostname,
port,
pem_file_path,
);
}
inline for (tls.ciphersuites.all) |ciphersuite| {
if (std.mem.eql(u8, ciphersuite_str, ciphersuite.name)) {
return try record_with_ciphersuite(
.{ciphersuite},
allocator,
out_name,
curve_str,
hostname,
port,
pem_file_path,
);
}
}
std.log.crit("Invalid ciphersuite `{s}`", .{ciphersuite_str});
std.debug.warn("Available options:\n- all\n", .{});
inline for (tls.ciphersuites.all) |ciphersuite| {
std.debug.warn("- {s}\n", .{ciphersuite.name});
}
return error.InvalidArg;
}

View file

@ -0,0 +1,14 @@
const Builder = @import("std").build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const lib = b.addStaticLibrary("iguanaTLS", "src/main.zig");
lib.setBuildMode(mode);
lib.install();
var main_tests = b.addTest("src/main.zig");
main_tests.setBuildMode(mode);
const test_step = b.step("test", "Run library tests");
test_step.dependOn(&main_tests.step);
}

View file

@ -0,0 +1,15 @@
pkgs:
iguanaTLS:
version: 0.0.1
author: alexnask
description: "Minimal, experimental TLS 1.2 implementation in Zig"
license: MIT
source_url: "https://github.com/alexnask/iguanaTLS"
files:
build.zig
README.md
LICENSE
src/*.zig
bench/*.zig
test/*

View file

@ -0,0 +1,631 @@
const std = @import("std");
const BigInt = std.math.big.int.Const;
const mem = std.mem;
const Allocator = mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
// zig fmt: off
pub const Tag = enum(u8) {
bool = 0x01,
int = 0x02,
bit_string = 0x03,
octet_string = 0x04,
@"null" = 0x05,
object_identifier = 0x06,
utf8_string = 0x0c,
printable_string = 0x13,
ia5_string = 0x16,
utc_time = 0x17,
bmp_string = 0x1e,
sequence = 0x30,
set = 0x31,
// Bogus value
context_specific = 0xff,
};
// zig fmt: on
pub const ObjectIdentifier = struct {
data: [16]u32,
len: u8,
};
pub const BitString = struct {
data: []const u8,
bit_len: usize,
};
pub const Value = union(Tag) {
bool: bool,
int: BigInt,
bit_string: BitString,
octet_string: []const u8,
@"null",
// @TODO Make this []u32, owned?
object_identifier: ObjectIdentifier,
utf8_string: []const u8,
printable_string: []const u8,
ia5_string: []const u8,
utc_time: []const u8,
bmp_string: []const u16,
sequence: []const @This(),
set: []const @This(),
context_specific: struct {
child: *const Value,
number: u8,
},
pub fn deinit(self: @This(), alloc: *Allocator) void {
switch (self) {
.int => |i| alloc.free(i.limbs),
.bit_string => |bs| alloc.free(bs.data),
.octet_string,
.utf8_string,
.printable_string,
.ia5_string,
.utc_time,
=> |s| alloc.free(s),
.bmp_string => |s| alloc.free(s),
.sequence, .set => |s| {
for (s) |c| {
c.deinit(alloc);
}
alloc.free(s);
},
.context_specific => |cs| {
cs.child.deinit(alloc);
alloc.destroy(cs.child);
},
else => {},
}
}
fn formatInternal(
self: Value,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
indents: usize,
writer: anytype,
) @TypeOf(writer).Error!void {
try writer.writeByteNTimes(' ', indents);
switch (self) {
.bool => |b| try writer.print("BOOLEAN {}\n", .{b}),
.int => |i| {
try writer.writeAll("INTEGER ");
try i.format(fmt, options, writer);
try writer.writeByte('\n');
},
.bit_string => |bs| {
try writer.print("BIT STRING ({} bits) ", .{bs.bit_len});
const bits_to_show = std.math.min(8 * 3, bs.bit_len);
const bytes = std.math.divCeil(usize, bits_to_show, 8) catch unreachable;
var bit_idx: usize = 0;
var byte_idx: usize = 0;
while (byte_idx < bytes) : (byte_idx += 1) {
const byte = bs.data[byte_idx];
var cur_bit_idx: u3 = 0;
while (bit_idx < bits_to_show) {
const mask = @as(u8, 0x80) >> cur_bit_idx;
try writer.print("{}", .{@boolToInt(byte & mask == mask)});
cur_bit_idx += 1;
bit_idx += 1;
if (cur_bit_idx == 7)
break;
}
}
if (bits_to_show != bs.bit_len)
try writer.writeAll("...");
try writer.writeByte('\n');
},
.octet_string => |s| try writer.print("OCTET STRING ({} bytes) {X}\n", .{ s.len, s }),
.@"null" => try writer.writeAll("NULL\n"),
.object_identifier => |oid| {
try writer.writeAll("OBJECT IDENTIFIER ");
var i: u8 = 0;
while (i < oid.len) : (i += 1) {
if (i != 0) try writer.writeByte('.');
try writer.print("{}", .{oid.data[i]});
}
try writer.writeByte('\n');
},
.utf8_string => |s| try writer.print("UTF8 STRING ({} bytes) {}\n", .{ s.len, s }),
.printable_string => |s| try writer.print("PRINTABLE STRING ({} bytes) {}\n", .{ s.len, s }),
.ia5_string => |s| try writer.print("IA5 STRING ({} bytes) {}\n", .{ s.len, s }),
.utc_time => |s| try writer.print("UTC TIME {}\n", .{s}),
.bmp_string => |s| try writer.print("BMP STRING ({} words) {}\n", .{
s.len,
@ptrCast([*]const u16, s.ptr)[0 .. s.len * 2],
}),
.sequence => |children| {
try writer.print("SEQUENCE ({} elems)\n", .{children.len});
for (children) |child| try child.formatInternal(fmt, options, indents + 2, writer);
},
.set => |children| {
try writer.print("SET ({} elems)\n", .{children.len});
for (children) |child| try child.formatInternal(fmt, options, indents + 2, writer);
},
.context_specific => |cs| {
try writer.print("[{}]\n", .{cs.number});
try cs.child.formatInternal(fmt, options, indents + 2, writer);
},
}
}
pub fn format(self: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
try self.formatInternal(fmt, options, 0, writer);
}
};
/// Distinguished encoding rules
pub const der = struct {
pub fn DecodeError(comptime Reader: type) type {
return Reader.Error || error{
OutOfMemory,
EndOfStream,
InvalidLength,
InvalidTag,
InvalidContainerLength,
DoesNotMatchSchema,
};
}
fn DERReaderState(comptime Reader: type) type {
return struct {
der_reader: Reader,
length: usize,
idx: usize = 0,
};
}
fn DERReader(comptime Reader: type) type {
const S = struct {
pub fn read(state: *DERReaderState(Reader), buffer: []u8) DecodeError(Reader)!usize {
const out_bytes = std.math.min(buffer.len, state.length - state.idx);
const res = try state.der_reader.readAll(buffer[0..out_bytes]);
state.idx += res;
return res;
}
};
return std.io.Reader(*DERReaderState(Reader), DecodeError(Reader), S.read);
}
pub fn parse_schema(
schema: anytype,
captures: anytype,
der_reader: anytype,
) !void {
const res = try parse_schema_tag_len_internal(null, null, schema, captures, der_reader);
if (res != null) return error.DoesNotMatchSchema;
}
pub fn parse_schema_tag_len(
existing_tag_byte: ?u8,
existing_length: ?usize,
schema: anytype,
captures: anytype,
der_reader: anytype,
) !void {
const res = try parse_schema_tag_len_internal(
existing_tag_byte,
existing_length,
schema,
captures,
der_reader,
);
if (res != null) return error.DoesNotMatchSchema;
}
const TagLength = struct {
tag: u8,
length: usize,
};
pub fn parse_schema_tag_len_internal(
existing_tag_byte: ?u8,
existing_length: ?usize,
schema: anytype,
captures: anytype,
der_reader: anytype,
) !?TagLength {
const Reader = @TypeOf(der_reader);
const isEnumLit = comptime std.meta.trait.is(.EnumLiteral);
comptime var tag_idx = 0;
const has_capture = comptime isEnumLit(@TypeOf(schema[tag_idx])) and schema[tag_idx] == .capture;
if (has_capture) tag_idx += 2;
const is_optional = comptime isEnumLit(@TypeOf(schema[tag_idx])) and schema[tag_idx] == .optional;
if (is_optional) tag_idx += 1;
const tag_literal = schema[tag_idx];
comptime std.debug.assert(isEnumLit(@TypeOf(tag_literal)));
const tag_byte = existing_tag_byte orelse (der_reader.readByte() catch |err| switch (err) {
error.EndOfStream => |e| return if (is_optional) null else error.EndOfStream,
else => |e| return e,
});
const length = existing_length orelse try parse_length(der_reader);
if (tag_literal == .sequence_of) {
if (tag_byte != @enumToInt(Tag.sequence)) {
if (is_optional) return TagLength{ .tag = tag_byte, .length = length };
return error.InvalidTag;
}
var curr_tag_length: ?TagLength = null;
const sub_schema = schema[tag_idx + 1];
while (true) {
if (curr_tag_length == null) {
curr_tag_length = .{
.tag = der_reader.readByte() catch |err| switch (err) {
error.EndOfStream => {
curr_tag_length = null;
break;
},
else => |e| return e,
},
.length = try parse_length(der_reader),
};
}
curr_tag_length = parse_schema_tag_len_internal(
curr_tag_length.?.tag,
curr_tag_length.?.length,
sub_schema,
captures,
der_reader,
) catch |err| switch (err) {
error.DoesNotMatchSchema => break,
else => |e| return e,
};
}
return curr_tag_length;
} else if (tag_literal == .any) {
if (!has_capture) {
try der_reader.skipBytes(length, .{});
return null;
}
var reader_state = DERReaderState(Reader){
.der_reader = der_reader,
.idx = 0,
.length = length,
};
var reader = DERReader(@TypeOf(der_reader)){ .context = &reader_state };
const capture_context = captures[schema[1] * 2];
const capture_action = captures[schema[1] * 2 + 1];
try capture_action(capture_context, tag_byte, length, reader);
// Skip remaining bytes
try der_reader.skipBytes(reader_state.length - reader_state.idx, .{});
return null;
} else if (tag_literal == .context_specific) {
const cs_number = schema[tag_idx + 1];
if (tag_byte & 0xC0 == 0x80 and tag_byte - 0xa0 == cs_number) {
if (!has_capture) {
if (schema.len > tag_idx + 2) {
return try parse_schema_tag_len_internal(null, null, schema[tag_idx + 2], captures, der_reader);
}
try der_reader.skipBytes(length, .{});
return null;
}
var reader_state = DERReaderState(Reader){
.der_reader = der_reader,
.idx = 0,
.length = length,
};
var reader = DERReader(Reader){ .context = &reader_state };
const capture_context = captures[schema[1] * 2];
const capture_action = captures[schema[1] * 2 + 1];
try capture_action(capture_context, tag_byte, length, reader);
// Skip remaining bytes
try der_reader.skipBytes(reader_state.length - reader_state.idx, .{});
return null;
} else if (is_optional)
return TagLength{ .tag = tag_byte, .length = length }
else
return error.DoesNotMatchSchema;
}
const schema_tag: Tag = tag_literal;
const actual_tag = std.meta.intToEnum(Tag, tag_byte) catch return error.InvalidTag;
if (actual_tag != schema_tag) {
if (is_optional) return TagLength{ .tag = tag_byte, .length = length };
return error.DoesNotMatchSchema;
}
const single_seq = schema_tag == .sequence and schema.len == 1;
if ((!has_capture and schema_tag != .sequence) or (!has_capture and single_seq)) {
try der_reader.skipBytes(length, .{});
return null;
}
if (has_capture) {
var reader_state = DERReaderState(Reader){
.der_reader = der_reader,
.idx = 0,
.length = length,
};
var reader = DERReader(Reader){ .context = &reader_state };
const capture_context = captures[schema[1] * 2];
const capture_action = captures[schema[1] * 2 + 1];
try capture_action(capture_context, tag_byte, length, reader);
// Skip remaining bytes
try der_reader.skipBytes(reader_state.length - reader_state.idx, .{});
return null;
}
var cur_tag_length: ?TagLength = null;
const sub_schemas = schema[tag_idx + 1];
comptime var i = 0;
inline while (i < sub_schemas.len) : (i += 1) {
const curr_tag = if (cur_tag_length) |tl| tl.tag else null;
const curr_length = if (cur_tag_length) |tl| tl.length else null;
cur_tag_length = try parse_schema_tag_len_internal(curr_tag, curr_length, sub_schemas[i], captures, der_reader);
}
return cur_tag_length;
}
pub const EncodedLength = struct {
data: [@sizeOf(usize) + 1]u8,
len: usize,
pub fn slice(self: @This()) []const u8 {
if (self.len == 1) return self.data[0..1];
return self.data[0 .. 1 + self.len];
}
};
pub fn encode_length(length: usize) EncodedLength {
var enc = EncodedLength{ .data = undefined, .len = 0 };
if (length < 128) {
enc.data[0] = @truncate(u8, length);
enc.len = 1;
} else {
const bytes_needed = @intCast(u8, std.math.divCeil(
usize,
std.math.log2_int_ceil(usize, length),
8,
) catch unreachable);
enc.data[0] = bytes_needed | 0x80;
mem.copy(
u8,
enc.data[1 .. bytes_needed + 1],
mem.asBytes(&length)[0..bytes_needed],
);
if (std.builtin.target.cpu.arch.endian() != .Big) {
mem.reverse(u8, enc.data[1 .. bytes_needed + 1]);
}
enc.len = bytes_needed;
}
return enc;
}
fn parse_int_internal(alloc: *Allocator, bytes_read: *usize, der_reader: anytype) !BigInt {
const length = try parse_length_internal(bytes_read, der_reader);
return try parse_int_with_length_internal(alloc, bytes_read, length, der_reader);
}
pub fn parse_int(alloc: *Allocator, der_reader: anytype) !BigInt {
var bytes: usize = undefined;
return try parse_int_internal(alloc, &bytes, der_reader);
}
pub fn parse_int_with_length(alloc: *Allocator, length: usize, der_reader: anytype) !BigInt {
var read: usize = 0;
return try parse_int_with_length_internal(alloc, &read, length, der_reader);
}
fn parse_int_with_length_internal(alloc: *Allocator, bytes_read: *usize, length: usize, der_reader: anytype) !BigInt {
const first_byte = try der_reader.readByte();
if (first_byte == 0x0 and length > 1) {
// Positive number with highest bit set to 1 in the rest.
const limb_count = std.math.divCeil(usize, length - 1, @sizeOf(usize)) catch unreachable;
const limbs = try alloc.alloc(usize, limb_count);
std.mem.set(usize, limbs, 0);
errdefer alloc.free(limbs);
var limb_ptr = @ptrCast([*]u8, limbs.ptr);
try der_reader.readNoEof(limb_ptr[0 .. length - 1]);
// We always reverse because the standard library big int expects little endian.
mem.reverse(u8, limb_ptr[0 .. length - 1]);
bytes_read.* += length;
return BigInt{ .limbs = limbs, .positive = true };
}
std.debug.assert(length != 0);
// Write first_byte
// Twos complement
const limb_count = std.math.divCeil(usize, length, @sizeOf(usize)) catch unreachable;
const limbs = try alloc.alloc(usize, limb_count);
std.mem.set(usize, limbs, 0);
errdefer alloc.free(limbs);
var limb_ptr = @ptrCast([*]u8, limbs.ptr);
limb_ptr[0] = first_byte & ~@as(u8, 0x80);
try der_reader.readNoEof(limb_ptr[1..length]);
// We always reverse because the standard library big int expects little endian.
mem.reverse(u8, limb_ptr[0..length]);
bytes_read.* += length;
return BigInt{ .limbs = limbs, .positive = (first_byte & 0x80) == 0x00 };
}
pub fn parse_length(der_reader: anytype) !usize {
var bytes: usize = 0;
return try parse_length_internal(&bytes, der_reader);
}
fn parse_length_internal(bytes_read: *usize, der_reader: anytype) !usize {
const first_byte = try der_reader.readByte();
bytes_read.* += 1;
if (first_byte & 0x80 == 0x00) {
// 1 byte value
return first_byte;
}
const length = @truncate(u7, first_byte);
if (length > @sizeOf(usize))
@panic("DER length does not fit in usize");
var res_buf = std.mem.zeroes([@sizeOf(usize)]u8);
try der_reader.readNoEof(res_buf[0..length]);
bytes_read.* += length;
if (std.builtin.target.cpu.arch.endian() != .Big) {
mem.reverse(u8, res_buf[0..length]);
}
return mem.bytesToValue(usize, &res_buf);
}
fn parse_value_with_tag_byte(
tag_byte: u8,
alloc: *Allocator,
bytes_read: *usize,
der_reader: anytype,
) DecodeError(@TypeOf(der_reader))!Value {
const tag = std.meta.intToEnum(Tag, tag_byte) catch {
// tag starts with '0b10...', this is the context specific class.
if (tag_byte & 0xC0 == 0x80) {
const length = try parse_length_internal(bytes_read, der_reader);
var cur_read_bytes: usize = 0;
var child = try alloc.create(Value);
errdefer alloc.destroy(child);
child.* = try parse_value_internal(alloc, &cur_read_bytes, der_reader);
if (cur_read_bytes != length)
return error.InvalidContainerLength;
bytes_read.* += length;
return Value{ .context_specific = .{ .child = child, .number = tag_byte - 0xa0 } };
}
return error.InvalidTag;
};
switch (tag) {
.bool => {
if ((try der_reader.readByte()) != 0x1)
return error.InvalidLength;
defer bytes_read.* += 2;
return Value{ .bool = (try der_reader.readByte()) != 0x0 };
},
.int => return Value{ .int = try parse_int_internal(alloc, bytes_read, der_reader) },
.bit_string => {
const length = try parse_length_internal(bytes_read, der_reader);
const unused_bits = try der_reader.readByte();
std.debug.assert(unused_bits < 8);
const bit_count = (length - 1) * 8 - unused_bits;
const bit_memory = try alloc.alloc(u8, std.math.divCeil(usize, bit_count, 8) catch unreachable);
errdefer alloc.free(bit_memory);
try der_reader.readNoEof(bit_memory[0 .. length - 1]);
bytes_read.* += length;
return Value{ .bit_string = .{ .data = bit_memory, .bit_len = bit_count } };
},
.octet_string, .utf8_string, .printable_string, .utc_time, .ia5_string => {
const length = try parse_length_internal(bytes_read, der_reader);
const str_mem = try alloc.alloc(u8, length);
try der_reader.readNoEof(str_mem);
bytes_read.* += length;
return @as(Value, switch (tag) {
.octet_string => .{ .octet_string = str_mem },
.utf8_string => .{ .utf8_string = str_mem },
.printable_string => .{ .printable_string = str_mem },
.utc_time => .{ .utc_time = str_mem },
.ia5_string => .{ .ia5_string = str_mem },
else => unreachable,
});
},
.@"null" => {
std.debug.assert((try parse_length_internal(bytes_read, der_reader)) == 0x00);
return .@"null";
},
.object_identifier => {
const length = try parse_length_internal(bytes_read, der_reader);
const first_byte = try der_reader.readByte();
var ret = Value{ .object_identifier = .{ .data = undefined, .len = 0 } };
ret.object_identifier.data[0] = first_byte / 40;
ret.object_identifier.data[1] = first_byte % 40;
var out_idx: u8 = 2;
var i: usize = 0;
while (i < length - 1) {
var current_value: u32 = 0;
var current_byte = try der_reader.readByte();
i += 1;
while (current_byte & 0x80 == 0x80) : (i += 1) {
// Increase the base of the previous bytes
current_value *= 128;
// Add the current byte in base 128
current_value += @as(u32, current_byte & ~@as(u8, 0x80)) * 128;
current_byte = try der_reader.readByte();
} else {
current_value += current_byte;
}
ret.object_identifier.data[out_idx] = current_value;
out_idx += 1;
}
ret.object_identifier.len = out_idx;
std.debug.assert(out_idx <= 16);
bytes_read.* += length;
return ret;
},
.bmp_string => {
const length = try parse_length_internal(bytes_read, der_reader);
const str_mem = try alloc.alloc(u16, @divExact(length, 2));
errdefer alloc.free(str_mem);
for (str_mem) |*wide_char| {
wide_char.* = try der_reader.readIntBig(u16);
}
bytes_read.* += length;
return Value{ .bmp_string = str_mem };
},
.sequence, .set => {
const length = try parse_length_internal(bytes_read, der_reader);
var cur_read_bytes: usize = 0;
var arr = std.ArrayList(Value).init(alloc);
errdefer arr.deinit();
while (cur_read_bytes < length) {
(try arr.addOne()).* = try parse_value_internal(alloc, &cur_read_bytes, der_reader);
}
if (cur_read_bytes != length)
return error.InvalidContainerLength;
bytes_read.* += length;
return @as(Value, switch (tag) {
.sequence => .{ .sequence = arr.toOwnedSlice() },
.set => .{ .set = arr.toOwnedSlice() },
else => unreachable,
});
},
.context_specific => unreachable,
}
}
fn parse_value_internal(alloc: *Allocator, bytes_read: *usize, der_reader: anytype) DecodeError(@TypeOf(der_reader))!Value {
const tag_byte = try der_reader.readByte();
bytes_read.* += 1;
return try parse_value_with_tag_byte(tag_byte, alloc, bytes_read, der_reader);
}
pub fn parse_value(alloc: *Allocator, der_reader: anytype) DecodeError(@TypeOf(der_reader))!Value {
var read: usize = 0;
return try parse_value_internal(alloc, &read, der_reader);
}
};
test "der.parse_value" {
const github_der = @embedFile("../test/github.der");
var fbs = std.io.fixedBufferStream(github_der);
var arena = ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
_ = try der.parse_value(&arena.allocator, fbs.reader());
}

View file

@ -0,0 +1,617 @@
const std = @import("std");
const mem = std.mem;
usingnamespace @import("crypto.zig");
const Chacha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305;
const Aes128Gcm = std.crypto.aead.aes_gcm.Aes128Gcm;
const main = @import("main.zig");
const alert_byte_to_error = main.alert_byte_to_error;
const record_tag_length = main.record_tag_length;
const record_length = main.record_length;
pub const suites = struct {
pub const ECDHE_RSA_Chacha20_Poly1305 = struct {
pub const name = "ECDHE-RSA-CHACHA20-POLY1305";
pub const tag = 0xCCA8;
pub const key_exchange = .ecdhe;
pub const hash = .sha256;
pub const Keys = struct {
client_key: [32]u8,
server_key: [32]u8,
client_iv: [12]u8,
server_iv: [12]u8,
};
pub const State = union(enum) {
none,
in_record: struct {
left: usize,
context: ChaCha20Stream.BlockVec,
idx: usize,
buf: [64]u8,
},
};
pub const default_state: State = .none;
pub fn raw_write(
comptime buffer_size: usize,
rand: *std.rand.Random,
key_data: anytype,
writer: anytype,
prefix: [3]u8,
seq: u64,
buffer: []const u8,
) !void {
std.debug.assert(buffer.len <= buffer_size);
try writer.writeAll(&prefix);
try writer.writeIntBig(u16, @intCast(u16, buffer.len + 16));
var additional_data: [13]u8 = undefined;
mem.writeIntBig(u64, additional_data[0..8], seq);
additional_data[8..11].* = prefix;
mem.writeIntBig(u16, additional_data[11..13], @intCast(u16, buffer.len));
var encrypted_data: [buffer_size]u8 = undefined;
var tag_data: [16]u8 = undefined;
var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8);
mem.writeIntBig(u64, nonce[4..12], seq);
for (nonce) |*n, i| {
n.* ^= key_data.client_iv(@This())[i];
}
Chacha20Poly1305.encrypt(
encrypted_data[0..buffer.len],
&tag_data,
buffer,
&additional_data,
nonce,
key_data.client_key(@This()).*,
);
try writer.writeAll(encrypted_data[0..buffer.len]);
try writer.writeAll(&tag_data);
}
pub fn check_verify_message(
key_data: anytype,
length: usize,
reader: anytype,
verify_message: [16]u8,
) !bool {
if (length != 32)
return false;
var msg_in: [32]u8 = undefined;
try reader.readNoEof(&msg_in);
const additional_data: [13]u8 = ([1]u8{0} ** 8) ++ [5]u8{ 0x16, 0x03, 0x03, 0x00, 0x10 };
var decrypted: [16]u8 = undefined;
Chacha20Poly1305.decrypt(
&decrypted,
msg_in[0..16],
msg_in[16..].*,
&additional_data,
key_data.server_iv(@This()).*,
key_data.server_key(@This()).*,
) catch return false;
return mem.eql(u8, &decrypted, &verify_message);
}
pub fn read(
comptime buf_size: usize,
state: *State,
key_data: anytype,
reader: anytype,
server_seq: *u64,
buffer: []u8,
) !usize {
switch (state.*) {
.none => {
const tag_length = record_tag_length(reader) catch |err| switch (err) {
error.EndOfStream => return 0,
else => |e| return e,
};
if (tag_length.length < 16)
return error.ServerMalformedResponse;
const len = tag_length.length - 16;
if ((tag_length.tag != 0x17 and tag_length.tag != 0x15) or
(tag_length.tag == 0x15 and len != 2))
{
return error.ServerMalformedResponse;
}
const curr_bytes = if (tag_length.tag == 0x15)
2
else
std.math.min(std.math.min(len, buf_size), buffer.len);
var nonce: [12]u8 = ([1]u8{0} ** 4) ++ ([1]u8{undefined} ** 8);
mem.writeIntBig(u64, nonce[4..12], server_seq.*);
for (nonce) |*n, i| {
n.* ^= key_data.server_iv(@This())[i];
}
var c: [4]u32 = undefined;
c[0] = 1;
c[1] = mem.readIntLittle(u32, nonce[0..4]);
c[2] = mem.readIntLittle(u32, nonce[4..8]);
c[3] = mem.readIntLittle(u32, nonce[8..12]);
const server_key = keyToWords(key_data.server_key(@This()).*);
var context = ChaCha20Stream.initContext(server_key, c);
var idx: usize = 0;
var buf: [64]u8 = undefined;
if (tag_length.tag == 0x15) {
var encrypted: [2]u8 = undefined;
reader.readNoEof(&encrypted) catch |err| switch (err) {
error.EndOfStream => return error.ServerMalformedResponse,
else => |e| return e,
};
var result: [2]u8 = undefined;
ChaCha20Stream.chacha20Xor(
&result,
&encrypted,
server_key,
&context,
&idx,
&buf,
);
reader.skipBytes(16, .{}) catch |err| switch (err) {
error.EndOfStream => return error.ServerMalformedResponse,
else => |e| return e,
};
server_seq.* += 1;
// CloseNotify
if (result[1] == 0)
return 0;
return alert_byte_to_error(result[1]);
} else if (tag_length.tag == 0x17) {
// Partially decrypt the data.
var encrypted: [buf_size]u8 = undefined;
const actually_read = try reader.read(encrypted[0..curr_bytes]);
ChaCha20Stream.chacha20Xor(
buffer[0..actually_read],
encrypted[0..actually_read],
server_key,
&context,
&idx,
&buf,
);
if (actually_read < len) {
state.* = .{
.in_record = .{
.left = len - actually_read,
.context = context,
.idx = idx,
.buf = buf,
},
};
} else {
// @TODO Verify Poly1305.
reader.skipBytes(16, .{}) catch |err| switch (err) {
error.EndOfStream => return error.ServerMalformedResponse,
else => |e| return e,
};
server_seq.* += 1;
}
return actually_read;
} else unreachable;
},
.in_record => |*record_info| {
const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);
// Partially decrypt the data.
var encrypted: [buf_size]u8 = undefined;
const actually_read = try reader.read(encrypted[0..curr_bytes]);
ChaCha20Stream.chacha20Xor(
buffer[0..actually_read],
encrypted[0..actually_read],
keyToWords(key_data.server_key(@This()).*),
&record_info.context,
&record_info.idx,
&record_info.buf,
);
record_info.left -= actually_read;
if (record_info.left == 0) {
// @TODO Verify Poly1305.
reader.skipBytes(16, .{}) catch |err| switch (err) {
error.EndOfStream => return error.ServerMalformedResponse,
else => |e| return e,
};
state.* = .none;
server_seq.* += 1;
}
return actually_read;
},
}
}
};
pub const ECDHE_RSA_AES128_GCM_SHA256 = struct {
pub const name = "ECDHE-RSA-AES128-GCM-SHA256";
pub const tag = 0xC02F;
pub const key_exchange = .ecdhe;
pub const hash = .sha256;
pub const Keys = struct {
client_key: [16]u8,
server_key: [16]u8,
client_iv: [4]u8,
server_iv: [4]u8,
};
const Aes = std.crypto.core.aes.Aes128;
pub const State = union(enum) {
none,
in_record: struct {
left: usize,
aes: @typeInfo(@TypeOf(Aes.initEnc)).Fn.return_type.?,
// ctr state
counterInt: u128,
idx: usize,
},
};
pub const default_state: State = .none;
pub fn check_verify_message(
key_data: anytype,
length: usize,
reader: anytype,
verify_message: [16]u8,
) !bool {
if (length != 40)
return false;
var iv: [12]u8 = undefined;
iv[0..4].* = key_data.server_iv(@This()).*;
try reader.readNoEof(iv[4..12]);
var msg_in: [32]u8 = undefined;
try reader.readNoEof(&msg_in);
const additional_data: [13]u8 = ([1]u8{0} ** 8) ++ [5]u8{ 0x16, 0x03, 0x03, 0x00, 0x10 };
var decrypted: [16]u8 = undefined;
Aes128Gcm.decrypt(
&decrypted,
msg_in[0..16],
msg_in[16..].*,
&additional_data,
iv,
key_data.server_key(@This()).*,
) catch return false;
return mem.eql(u8, &decrypted, &verify_message);
}
pub fn raw_write(
comptime buffer_size: usize,
rand: *std.rand.Random,
key_data: anytype,
writer: anytype,
prefix: [3]u8,
seq: u64,
buffer: []const u8,
) !void {
std.debug.assert(buffer.len <= buffer_size);
var iv: [12]u8 = undefined;
iv[0..4].* = key_data.client_iv(@This()).*;
rand.bytes(iv[4..12]);
var additional_data: [13]u8 = undefined;
mem.writeIntBig(u64, additional_data[0..8], seq);
additional_data[8..11].* = prefix;
mem.writeIntBig(u16, additional_data[11..13], @intCast(u16, buffer.len));
try writer.writeAll(&prefix);
try writer.writeIntBig(u16, @intCast(u16, buffer.len + 24));
try writer.writeAll(iv[4..12]);
var encrypted_data: [buffer_size]u8 = undefined;
var tag_data: [16]u8 = undefined;
Aes128Gcm.encrypt(
encrypted_data[0..buffer.len],
&tag_data,
buffer,
&additional_data,
iv,
key_data.client_key(@This()).*,
);
try writer.writeAll(encrypted_data[0..buffer.len]);
try writer.writeAll(&tag_data);
}
pub fn read(
comptime buf_size: usize,
state: *State,
key_data: anytype,
reader: anytype,
server_seq: *u64,
buffer: []u8,
) !usize {
switch (state.*) {
.none => {
const tag_length = record_tag_length(reader) catch |err| switch (err) {
error.EndOfStream => return 0,
else => |e| return e,
};
if (tag_length.length < 24)
return error.ServerMalformedResponse;
const len = tag_length.length - 24;
if ((tag_length.tag != 0x17 and tag_length.tag != 0x15) or
(tag_length.tag == 0x15 and len != 2))
{
return error.ServerMalformedResponse;
}
const curr_bytes = if (tag_length.tag == 0x15)
2
else
std.math.min(std.math.min(len, buf_size), buffer.len);
var iv: [12]u8 = undefined;
iv[0..4].* = key_data.server_iv(@This()).*;
reader.readNoEof(iv[4..12]) catch |err| switch (err) {
error.EndOfStream => return 0,
else => |e| return e,
};
const aes = Aes.initEnc(key_data.server_key(@This()).*);
var j: [16]u8 = undefined;
mem.copy(u8, j[0..12], iv[0..]);
mem.writeIntBig(u32, j[12..][0..4], 2);
var counterInt = mem.readInt(u128, &j, .Big);
var idx: usize = 0;
if (tag_length.tag == 0x15) {
var encrypted: [2]u8 = undefined;
reader.readNoEof(&encrypted) catch |err| switch (err) {
error.EndOfStream => return error.ServerMalformedResponse,
else => |e| return e,
};
var result: [2]u8 = undefined;
ctr(
@TypeOf(aes),
aes,
&result,
&encrypted,
&counterInt,
&idx,
.Big,
);
reader.skipBytes(16, .{}) catch |err| switch (err) {
error.EndOfStream => return error.ServerMalformedResponse,
else => |e| return e,
};
server_seq.* += 1;
// CloseNotify
if (result[1] == 0)
return 0;
return alert_byte_to_error(result[1]);
} else if (tag_length.tag == 0x17) {
// Partially decrypt the data.
var encrypted: [buf_size]u8 = undefined;
const actually_read = try reader.read(encrypted[0..curr_bytes]);
ctr(
@TypeOf(aes),
aes,
buffer[0..actually_read],
encrypted[0..actually_read],
&counterInt,
&idx,
.Big,
);
if (actually_read < len) {
state.* = .{
.in_record = .{
.left = len - actually_read,
.aes = aes,
.counterInt = counterInt,
.idx = idx,
},
};
} else {
// @TODO Verify the message
reader.skipBytes(16, .{}) catch |err| switch (err) {
error.EndOfStream => return error.ServerMalformedResponse,
else => |e| return e,
};
server_seq.* += 1;
}
return actually_read;
} else unreachable;
},
.in_record => |*record_info| {
const curr_bytes = std.math.min(std.math.min(buf_size, buffer.len), record_info.left);
// Partially decrypt the data.
var encrypted: [buf_size]u8 = undefined;
const actually_read = try reader.read(encrypted[0..curr_bytes]);
ctr(
@TypeOf(record_info.aes),
record_info.aes,
buffer[0..actually_read],
encrypted[0..actually_read],
&record_info.counterInt,
&record_info.idx,
.Big,
);
record_info.left -= actually_read;
if (record_info.left == 0) {
// @TODO Verify Poly1305.
reader.skipBytes(16, .{}) catch |err| switch (err) {
error.EndOfStream => return error.ServerMalformedResponse,
else => |e| return e,
};
state.* = .none;
server_seq.* += 1;
}
return actually_read;
},
}
}
};
pub const all = &[_]type{ ECDHE_RSA_Chacha20_Poly1305, ECDHE_RSA_AES128_GCM_SHA256 };
};
fn key_field_width(comptime T: type, comptime field: anytype) ?usize {
if (!@hasField(T, @tagName(field)))
return null;
const field_info = std.meta.fieldInfo(T, field);
if (!comptime std.meta.trait.is(.Array)(field_info.field_type) or std.meta.Elem(field_info.field_type) != u8)
@compileError("Field '" ++ field ++ "' of type '" ++ @typeName(T) ++ "' should be an array of u8.");
return @typeInfo(field_info.field_type).Array.len;
}
pub fn key_data_size(comptime ciphersuites: anytype) usize {
var max: usize = 0;
for (ciphersuites) |cs| {
const curr = (key_field_width(cs.Keys, .client_mac) orelse 0) +
(key_field_width(cs.Keys, .server_mac) orelse 0) +
key_field_width(cs.Keys, .client_key).? +
key_field_width(cs.Keys, .server_key).? +
key_field_width(cs.Keys, .client_iv).? +
key_field_width(cs.Keys, .server_iv).?;
if (curr > max)
max = curr;
}
return max;
}
pub fn KeyData(comptime ciphersuites: anytype) type {
return struct {
data: [key_data_size(ciphersuites)]u8,
pub fn client_mac(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_mac) orelse 0]u8 {
return self.data[0..comptime (key_field_width(cs.Keys, .client_mac) orelse 0)];
}
pub fn server_mac(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_mac) orelse 0]u8 {
const start = key_field_width(cs.Keys, .client_mac) orelse 0;
return self.data[start..][0..comptime (key_field_width(cs.Keys, .server_mac) orelse 0)];
}
pub fn client_key(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_key).?]u8 {
const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
(key_field_width(cs.Keys, .server_mac) orelse 0);
return self.data[start..][0..comptime key_field_width(cs.Keys, .client_key).?];
}
pub fn server_key(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_key).?]u8 {
const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
(key_field_width(cs.Keys, .server_mac) orelse 0) +
key_field_width(cs.Keys, .client_key).?;
return self.data[start..][0..comptime key_field_width(cs.Keys, .server_key).?];
}
pub fn client_iv(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .client_iv).?]u8 {
const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
(key_field_width(cs.Keys, .server_mac) orelse 0) +
key_field_width(cs.Keys, .client_key).? +
key_field_width(cs.Keys, .server_key).?;
return self.data[start..][0..comptime key_field_width(cs.Keys, .client_iv).?];
}
pub fn server_iv(self: *@This(), comptime cs: type) *[key_field_width(cs.Keys, .server_iv).?]u8 {
const start = (key_field_width(cs.Keys, .client_mac) orelse 0) +
(key_field_width(cs.Keys, .server_mac) orelse 0) +
key_field_width(cs.Keys, .client_key).? +
key_field_width(cs.Keys, .server_key).? +
key_field_width(cs.Keys, .client_iv).?;
return self.data[start..][0..comptime key_field_width(cs.Keys, .server_iv).?];
}
};
}
pub fn key_expansion(
comptime ciphersuites: anytype,
tag: u16,
context: anytype,
comptime next_32_bytes: anytype,
) KeyData(ciphersuites) {
var res: KeyData(ciphersuites) = undefined;
inline for (ciphersuites) |cs| {
if (cs.tag == tag) {
var chunk: [32]u8 = undefined;
next_32_bytes(context, 0, &chunk);
comptime var chunk_idx = 1;
comptime var data_cursor = 0;
comptime var chunk_cursor = 0;
const fields = .{
.client_mac, .server_mac,
.client_key, .server_key,
.client_iv, .server_iv,
};
inline for (fields) |field| {
if (chunk_cursor == 32) {
next_32_bytes(context, chunk_idx, &chunk);
chunk_idx += 1;
chunk_cursor = 0;
}
const field_width = comptime (key_field_width(cs.Keys, field) orelse 0);
const first_read = comptime std.math.min(32 - chunk_cursor, field_width);
const second_read = field_width - first_read;
res.data[data_cursor..][0..first_read].* = chunk[chunk_cursor..][0..first_read].*;
data_cursor += first_read;
chunk_cursor += first_read;
if (second_read != 0) {
next_32_bytes(context, chunk_idx, &chunk);
chunk_idx += 1;
res.data[data_cursor..][0..second_read].* = chunk[chunk_cursor..][0..second_read].*;
data_cursor += second_read;
chunk_cursor = second_read;
comptime std.debug.assert(chunk_cursor != 32);
}
}
return res;
}
}
unreachable;
}
pub fn ClientState(comptime ciphersuites: anytype) type {
var fields: [ciphersuites.len]std.builtin.TypeInfo.UnionField = undefined;
for (ciphersuites) |cs, i| {
fields[i] = .{
.name = cs.name,
.field_type = cs.State,
.alignment = if (@sizeOf(cs.State) > 0) @alignOf(cs.State) else 0,
};
}
return @Type(.{
.Union = .{
.layout = .Extern,
.tag_type = null,
.fields = &fields,
.decls = &[0]std.builtin.TypeInfo.Declaration{},
},
});
}
pub fn client_state_default(comptime ciphersuites: anytype, tag: u16) ClientState(ciphersuites) {
inline for (ciphersuites) |cs| {
if (cs.tag == tag) {
return @unionInit(ClientState(ciphersuites), cs.name, cs.default_state);
}
}
unreachable;
}

View file

@ -0,0 +1,980 @@
const std = @import("std");
const mem = std.mem;
// TODO See stdlib, this is a modified non vectorized implementation
pub const ChaCha20Stream = struct {
const math = std.math;
pub const BlockVec = [16]u32;
pub fn initContext(key: [8]u32, d: [4]u32) BlockVec {
const c = "expand 32-byte k";
const constant_le = comptime [4]u32{
mem.readIntLittle(u32, c[0..4]),
mem.readIntLittle(u32, c[4..8]),
mem.readIntLittle(u32, c[8..12]),
mem.readIntLittle(u32, c[12..16]),
};
return BlockVec{
constant_le[0], constant_le[1], constant_le[2], constant_le[3],
key[0], key[1], key[2], key[3],
key[4], key[5], key[6], key[7],
d[0], d[1], d[2], d[3],
};
}
const QuarterRound = struct {
a: usize,
b: usize,
c: usize,
d: usize,
};
fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
return QuarterRound{
.a = a,
.b = b,
.c = c,
.d = d,
};
}
fn chacha20Core(x: *BlockVec, input: BlockVec) callconv(.Inline) void {
x.* = input;
const rounds = comptime [_]QuarterRound{
Rp(0, 4, 8, 12),
Rp(1, 5, 9, 13),
Rp(2, 6, 10, 14),
Rp(3, 7, 11, 15),
Rp(0, 5, 10, 15),
Rp(1, 6, 11, 12),
Rp(2, 7, 8, 13),
Rp(3, 4, 9, 14),
};
comptime var j: usize = 0;
inline while (j < 20) : (j += 2) {
inline for (rounds) |r| {
x[r.a] +%= x[r.b];
x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 16));
x[r.c] +%= x[r.d];
x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 12));
x[r.a] +%= x[r.b];
x[r.d] = math.rotl(u32, x[r.d] ^ x[r.a], @as(u32, 8));
x[r.c] +%= x[r.d];
x[r.b] = math.rotl(u32, x[r.b] ^ x[r.c], @as(u32, 7));
}
}
}
fn hashToBytes(out: *[64]u8, x: BlockVec) callconv(.Inline) void {
var i: usize = 0;
while (i < 4) : (i += 1) {
mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i * 4 + 0]);
mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i * 4 + 1]);
mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i * 4 + 2]);
mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i * 4 + 3]);
}
}
fn contextFeedback(x: *BlockVec, ctx: BlockVec) callconv(.Inline) void {
var i: usize = 0;
while (i < 16) : (i += 1) {
x[i] +%= ctx[i];
}
}
// TODO: Optimize this
pub fn chacha20Xor(out: []u8, in: []const u8, key: [8]u32, ctx: *BlockVec, idx: *usize, buf: *[64]u8) void {
var x: BlockVec = undefined;
const start_idx = idx.*;
var i: usize = 0;
while (i < in.len) {
if (idx.* % 64 == 0) {
if (idx.* != 0) {
ctx.*[12] += 1;
}
chacha20Core(x[0..], ctx.*);
contextFeedback(&x, ctx.*);
hashToBytes(buf, x);
}
out[i] = in[i] ^ buf[idx.* % 64];
i += 1;
idx.* += 1;
}
}
};
pub fn keyToWords(key: [32]u8) [8]u32 {
var k: [8]u32 = undefined;
var i: usize = 0;
while (i < 8) : (i += 1) {
k[i] = mem.readIntLittle(u32, key[i * 4 ..][0..4]);
}
return k;
}
// See std.crypto.core.modes.ctr
/// This mode creates a key stream by encrypting an incrementing counter using a block cipher, and adding it to the source material.
pub fn ctr(
comptime BlockCipher: anytype,
block_cipher: BlockCipher,
dst: []u8,
src: []const u8,
counterInt: *u128,
idx: *usize,
endian: comptime std.builtin.Endian,
) void {
std.debug.assert(dst.len >= src.len);
const block_length = BlockCipher.block_length;
var cur_idx: usize = 0;
const offset = idx.* % block_length;
if (offset != 0) {
const part_len = std.math.min(block_length - offset, src.len);
var counter: [BlockCipher.block_length]u8 = undefined;
mem.writeInt(u128, &counter, counterInt.*, endian);
var pad = [_]u8{0} ** block_length;
mem.copy(u8, pad[offset..], src[0..part_len]);
block_cipher.xor(&pad, &pad, counter);
mem.copy(u8, dst[0..part_len], pad[offset..][0..part_len]);
cur_idx += part_len;
idx.* += part_len;
if (idx.* % block_length == 0)
counterInt.* += 1;
}
const start_idx = cur_idx;
const remaining = src.len - cur_idx;
cur_idx = 0;
const parallel_count = BlockCipher.block.parallel.optimal_parallel_blocks;
const wide_block_length = parallel_count * 16;
if (remaining >= wide_block_length) {
var counters: [parallel_count * 16]u8 = undefined;
while (cur_idx + wide_block_length <= remaining) : (cur_idx += wide_block_length) {
comptime var j = 0;
inline while (j < parallel_count) : (j += 1) {
mem.writeInt(u128, counters[j * 16 .. j * 16 + 16], counterInt.*, endian);
counterInt.* +%= 1;
}
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);
idx.* += wide_block_length;
}
}
while (cur_idx + block_length <= remaining) : (cur_idx += block_length) {
var counter: [BlockCipher.block_length]u8 = undefined;
mem.writeInt(u128, &counter, counterInt.*, endian);
counterInt.* +%= 1;
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);
idx.* += block_length;
}
if (cur_idx < remaining) {
std.debug.assert(idx.* % block_length == 0);
var counter: [BlockCipher.block_length]u8 = undefined;
mem.writeInt(u128, &counter, counterInt.*, endian);
var pad = [_]u8{0} ** block_length;
mem.copy(u8, &pad, src[start_idx..][cur_idx..]);
block_cipher.xor(&pad, &pad, counter);
mem.copy(u8, dst[start_idx..][cur_idx..], pad[0 .. remaining - cur_idx]);
idx.* += remaining - cur_idx;
if (idx.* % block_length == 0)
counterInt.* +%= 1;
}
}
// Ported from BearSSL's ec_prime_i31 engine
pub const ecc = struct {
pub const SECP384R1 = struct {
pub const point_len = 96;
const order = [point_len / 2]u8{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xC7, 0x63, 0x4D, 0x81, 0xF4, 0x37, 0x2D, 0xDF,
0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A,
0xEC, 0xEC, 0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x73,
};
const P = [_]u32{
0x0000018C, 0x7FFFFFFF, 0x00000001, 0x00000000,
0x7FFFFFF8, 0x7FFFFFEF, 0x7FFFFFFF, 0x7FFFFFFF,
0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF,
0x7FFFFFFF, 0x00000FFF,
};
const R2 = [_]u32{
0x0000018C, 0x00000000, 0x00000080, 0x7FFFFE00,
0x000001FF, 0x00000800, 0x00000000, 0x7FFFE000,
0x00001FFF, 0x00008000, 0x00008000, 0x00000000,
0x00000000, 0x00000000,
};
const B = [_]u32{
0x0000018C, 0x6E666840, 0x070D0392, 0x5D810231,
0x7651D50C, 0x17E218D6, 0x1B192002, 0x44EFE441,
0x3A524E2B, 0x2719BA5F, 0x41F02209, 0x36C5643E,
0x5813EFFE, 0x000008A5,
};
const base_point = [point_len]u8{
0xAA, 0x87, 0xCA, 0x22, 0xBE, 0x8B, 0x05, 0x37,
0x8E, 0xB1, 0xC7, 0x1E, 0xF3, 0x20, 0xAD, 0x74,
0x6E, 0x1D, 0x3B, 0x62, 0x8B, 0xA7, 0x9B, 0x98,
0x59, 0xF7, 0x41, 0xE0, 0x82, 0x54, 0x2A, 0x38,
0x55, 0x02, 0xF2, 0x5D, 0xBF, 0x55, 0x29, 0x6C,
0x3A, 0x54, 0x5E, 0x38, 0x72, 0x76, 0x0A, 0xB7,
0x36, 0x17, 0xDE, 0x4A, 0x96, 0x26, 0x2C, 0x6F,
0x5D, 0x9E, 0x98, 0xBF, 0x92, 0x92, 0xDC, 0x29,
0xF8, 0xF4, 0x1D, 0xBD, 0x28, 0x9A, 0x14, 0x7C,
0xE9, 0xDA, 0x31, 0x13, 0xB5, 0xF0, 0xB8, 0xC0,
0x0A, 0x60, 0xB1, 0xCE, 0x1D, 0x7E, 0x81, 0x9D,
0x7A, 0x43, 0x1D, 0x7C, 0x90, 0xEA, 0x0E, 0x5F,
};
comptime {
std.debug.assert((P[0] - (P[0] >> 5) + 7) >> 2 == point_len + 1);
}
};
pub const SECP256R1 = struct {
pub const point_len = 64;
const order = [point_len / 2]u8{
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51,
};
const P = [_]u32{
0x00000108, 0x7FFFFFFF,
0x7FFFFFFF, 0x7FFFFFFF,
0x00000007, 0x00000000,
0x00000000, 0x00000040,
0x7FFFFF80, 0x000000FF,
};
const R2 = [_]u32{
0x00000108, 0x00014000,
0x00018000, 0x00000000,
0x7FF40000, 0x7FEFFFFF,
0x7FF7FFFF, 0x7FAFFFFF,
0x005FFFFF, 0x00000000,
};
const B = [_]u32{
0x00000108, 0x6FEE1803,
0x6229C4BD, 0x21B139BE,
0x327150AA, 0x3567802E,
0x3F7212ED, 0x012E4355,
0x782DD38D, 0x0000000E,
};
const base_point = [point_len]u8{
0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47,
0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40, 0xF2,
0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0,
0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2, 0x96,
0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B,
0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16,
0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE,
0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5,
};
comptime {
std.debug.assert((P[0] - (P[0] >> 5) + 7) >> 2 == point_len + 1);
}
};
fn jacobian_len(comptime Curve: type) usize {
return @divTrunc(Curve.order.len * 8 + 61, 31);
}
fn Jacobian(comptime Curve: type) type {
return [3][jacobian_len(Curve)]u32;
}
fn zero_jacobian(comptime Curve: type) Jacobian(Curve) {
var result = std.mem.zeroes(Jacobian(Curve));
result[0][0] = Curve.P[0];
result[1][0] = Curve.P[0];
result[2][0] = Curve.P[0];
return result;
}
pub fn scalarmult(
comptime Curve: type,
point: [Curve.point_len]u8,
k: []const u8,
) ![Curve.point_len]u8 {
var P: Jacobian(Curve) = undefined;
var res: u32 = decode_to_jacobian(Curve, &P, point);
point_mul(Curve, &P, k);
var out: [Curve.point_len]u8 = undefined;
encode_from_jacobian(Curve, &out, P);
if (res == 0)
return error.MultiplicationFailed;
return out;
}
pub fn KeyPair(comptime Curve: type) type {
return struct {
public_key: [Curve.point_len]u8,
secret_key: [Curve.point_len / 2]u8,
};
}
pub fn make_key_pair(comptime Curve: type, rand_bytes: [Curve.point_len / 2]u8) KeyPair(Curve) {
var key_bytes = rand_bytes;
comptime var mask: u8 = 0xFF;
comptime {
while (mask >= Curve.order[0]) {
mask >>= 1;
}
}
key_bytes[0] &= mask;
key_bytes[Curve.point_len / 2 - 1] |= 0x01;
return .{
.secret_key = key_bytes,
.public_key = scalarmult(Curve, Curve.base_point, &key_bytes) catch unreachable,
};
}
fn jacobian_with_one_set(comptime Curve: type, comptime fields: [2][jacobian_len(Curve)]u32) Jacobian(Curve) {
comptime const plen = (Curve.P[0] + 63) >> 5;
return fields ++ [1][jacobian_len(Curve)]u32{
[2]u32{ Curve.P[0], 1 } ++ ([1]u32{0} ** (plen - 2)),
};
}
fn encode_from_jacobian(comptime Curve: type, point: *[Curve.point_len]u8, P: Jacobian(Curve)) void {
var Q = P;
const T = comptime jacobian_with_one_set(Curve, [2][jacobian_len(Curve)]u32{ undefined, undefined });
_ = run_code(Curve, &Q, T, &code.affine);
encode_jacobian_part(point[0 .. Curve.point_len / 2], &Q[0]);
encode_jacobian_part(point[Curve.point_len / 2 ..], &Q[1]);
}
fn point_mul(comptime Curve: type, P: *Jacobian(Curve), x: []const u8) void {
var P2 = P.*;
point_double(Curve, &P2);
var P3 = P.*;
point_add(Curve, &P3, P2);
var Q = zero_jacobian(Curve);
var qz: u32 = 1;
var xlen = x.len;
var xidx: usize = 0;
while (xlen > 0) : ({
xlen -= 1;
xidx += 1;
}) {
var k: u3 = 6;
while (true) : (k -= 2) {
point_double(Curve, &Q);
point_double(Curve, &Q);
var T = P.*;
var U = Q;
const bits = @as(u32, x[xidx] >> k) & 3;
const bnz = NEQ(bits, 0);
CCOPY(EQ(bits, 2), mem.asBytes(&T), mem.asBytes(&P2));
CCOPY(EQ(bits, 3), mem.asBytes(&T), mem.asBytes(&P3));
point_add(Curve, &U, T);
CCOPY(bnz & qz, mem.asBytes(&Q), mem.asBytes(&T));
CCOPY(bnz & ~qz, mem.asBytes(&Q), mem.asBytes(&U));
qz &= ~bnz;
if (k == 0)
break;
}
}
P.* = Q;
}
fn point_double(comptime Curve: type, P: *Jacobian(Curve)) callconv(.Inline) void {
_ = run_code(Curve, P, P.*, &code.double);
}
fn point_add(comptime Curve: type, P1: *Jacobian(Curve), P2: Jacobian(Curve)) callconv(.Inline) void {
_ = run_code(Curve, P1, P2, &code._add);
}
fn decode_to_jacobian(
comptime Curve: type,
out: *Jacobian(Curve),
point: [Curve.point_len]u8,
) u32 {
out.* = zero_jacobian(Curve);
var result = decode_mod(Curve, &out.*[0], point[0 .. Curve.point_len / 2].*);
result &= decode_mod(Curve, &out.*[1], point[Curve.point_len / 2 ..].*);
const zlen = comptime ((Curve.P[0] + 63) >> 5);
comptime std.debug.assert(zlen == @typeInfo(@TypeOf(Curve.R2)).Array.len);
comptime std.debug.assert(zlen == @typeInfo(@TypeOf(Curve.B)).Array.len);
const Q = comptime jacobian_with_one_set(Curve, [2][jacobian_len(Curve)]u32{ Curve.R2, Curve.B });
result &= ~run_code(Curve, out, Q, &code.check);
return result;
}
const code = struct {
const P1x = 0;
const P1y = 1;
const P1z = 2;
const P2x = 3;
const P2y = 4;
const P2z = 5;
const Px = 0;
const Py = 1;
const Pz = 2;
const t1 = 6;
const t2 = 7;
const t3 = 8;
const t4 = 9;
const t5 = 10;
const t6 = 11;
const t7 = 12;
const t8 = 3;
const t9 = 4;
const t10 = 5;
fn MSET(comptime d: u16, comptime a: u16) u16 {
return 0x0000 + (d << 8) + (a << 4);
}
fn MADD(comptime d: u16, comptime a: u16) u16 {
return 0x1000 + (d << 8) + (a << 4);
}
fn MSUB(comptime d: u16, comptime a: u16) u16 {
return 0x2000 + (d << 8) + (a << 4);
}
fn MMUL(comptime d: u16, comptime a: u16, comptime b: u16) u16 {
return 0x3000 + (d << 8) + (a << 4) + b;
}
fn MINV(comptime d: u16, comptime a: u16, comptime b: u16) u16 {
return 0x4000 + (d << 8) + (a << 4) + b;
}
fn MTZ(comptime d: u16) u16 {
return 0x5000 + (d << 8);
}
const ENDCODE = 0;
const check = [_]u16{
// Convert x and y to Montgomery representation.
MMUL(t1, P1x, P2x),
MMUL(t2, P1y, P2x),
MSET(P1x, t1),
MSET(P1y, t2),
// Compute x^3 in t1.
MMUL(t2, P1x, P1x),
MMUL(t1, P1x, t2),
// Subtract 3*x from t1.
MSUB(t1, P1x),
MSUB(t1, P1x),
MSUB(t1, P1x),
// Add b.
MADD(t1, P2y),
// Compute y^2 in t2.
MMUL(t2, P1y, P1y),
// Compare y^2 with x^3 - 3*x + b; they must match.
MSUB(t1, t2),
MTZ(t1),
// Set z to 1 (in Montgomery representation).
MMUL(P1z, P2x, P2z),
ENDCODE,
};
const double = [_]u16{
// Compute z^2 (in t1).
MMUL(t1, Pz, Pz),
// Compute x-z^2 (in t2) and then x+z^2 (in t1).
MSET(t2, Px),
MSUB(t2, t1),
MADD(t1, Px),
// Compute m = 3*(x+z^2)*(x-z^2) (in t1).
MMUL(t3, t1, t2),
MSET(t1, t3),
MADD(t1, t3),
MADD(t1, t3),
// Compute s = 4*x*y^2 (in t2) and 2*y^2 (in t3).
MMUL(t3, Py, Py),
MADD(t3, t3),
MMUL(t2, Px, t3),
MADD(t2, t2),
// Compute x' = m^2 - 2*s.
MMUL(Px, t1, t1),
MSUB(Px, t2),
MSUB(Px, t2),
// Compute z' = 2*y*z.
MMUL(t4, Py, Pz),
MSET(Pz, t4),
MADD(Pz, t4),
// Compute y' = m*(s - x') - 8*y^4. Note that we already have
// 2*y^2 in t3.
MSUB(t2, Px),
MMUL(Py, t1, t2),
MMUL(t4, t3, t3),
MSUB(Py, t4),
MSUB(Py, t4),
ENDCODE,
};
const _add = [_]u16{
// Compute u1 = x1*z2^2 (in t1) and s1 = y1*z2^3 (in t3).
MMUL(t3, P2z, P2z),
MMUL(t1, P1x, t3),
MMUL(t4, P2z, t3),
MMUL(t3, P1y, t4),
// Compute u2 = x2*z1^2 (in t2) and s2 = y2*z1^3 (in t4).
MMUL(t4, P1z, P1z),
MMUL(t2, P2x, t4),
MMUL(t5, P1z, t4),
MMUL(t4, P2y, t5),
//Compute h = u2 - u1 (in t2) and r = s2 - s1 (in t4).
MSUB(t2, t1),
MSUB(t4, t3),
// Report cases where r = 0 through the returned flag.
MTZ(t4),
// Compute u1*h^2 (in t6) and h^3 (in t5).
MMUL(t7, t2, t2),
MMUL(t6, t1, t7),
MMUL(t5, t7, t2),
// Compute x3 = r^2 - h^3 - 2*u1*h^2.
// t1 and t7 can be used as scratch registers.
MMUL(P1x, t4, t4),
MSUB(P1x, t5),
MSUB(P1x, t6),
MSUB(P1x, t6),
//Compute y3 = r*(u1*h^2 - x3) - s1*h^3.
MSUB(t6, P1x),
MMUL(P1y, t4, t6),
MMUL(t1, t5, t3),
MSUB(P1y, t1),
//Compute z3 = h*z1*z2.
MMUL(t1, P1z, P2z),
MMUL(P1z, t1, t2),
ENDCODE,
};
const affine = [_]u16{
// Save z*R in t1.
MSET(t1, P1z),
// Compute z^3 in t2.
MMUL(t2, P1z, P1z),
MMUL(t3, P1z, t2),
MMUL(t2, t3, P2z),
// Invert to (1/z^3) in t2.
MINV(t2, t3, t4),
// Compute y.
MSET(t3, P1y),
MMUL(P1y, t2, t3),
// Compute (1/z^2) in t3.
MMUL(t3, t2, t1),
// Compute x.
MSET(t2, P1x),
MMUL(P1x, t2, t3),
ENDCODE,
};
};
fn decode_mod(
comptime Curve: type,
x: *[jacobian_len(Curve)]u32,
src: [Curve.point_len / 2]u8,
) u32 {
const mlen = comptime ((Curve.P[0] + 31) >> 5);
const tlen = comptime std.math.max(mlen << 2, Curve.point_len / 2) + 4;
var r: u32 = 0;
var pass: usize = 0;
while (pass < 2) : (pass += 1) {
var v: usize = 1;
var acc: u32 = 0;
var acc_len: u32 = 0;
var u: usize = 0;
while (u < tlen) : (u += 1) {
const b = if (u < Curve.point_len / 2)
@as(u32, src[Curve.point_len / 2 - 1 - u])
else
0;
acc |= b << @truncate(u5, acc_len);
acc_len += 8;
if (acc_len >= 31) {
const xw = acc & 0x7FFFFFFF;
acc_len -= 31;
acc = b >> @truncate(u5, 8 - acc_len);
if (v <= mlen) {
if (pass != 0) {
x[v] = r & xw;
} else {
const cc = @bitCast(u32, CMP(xw, Curve.P[v]));
r = MUX(EQ(cc, 0), r, cc);
}
} else if (pass == 0) {
r = MUX(EQ(xw, 0), r, 1);
}
v += 1;
}
}
r >>= 1;
r |= (r << 1);
}
x[0] = Curve.P[0];
return r & 1;
}
fn run_code(
comptime Curve: type,
P1: *Jacobian(Curve),
P2: Jacobian(Curve),
comptime Code: []const u16,
) u32 {
comptime const jaclen = jacobian_len(Curve);
var t: [13][jaclen]u32 = undefined;
var result: u32 = 1;
t[0..3].* = P1.*;
t[3..6].* = P2;
comptime var u: usize = 0;
inline while (true) : (u += 1) {
comptime var op = Code[u];
if (op == 0)
break;
comptime const d = (op >> 8) & 0x0F;
comptime const a = (op >> 4) & 0x0F;
comptime const b = op & 0x0F;
op >>= 12;
switch (op) {
0 => t[d] = t[a],
1 => {
var ctl = add(&t[d], &t[a], 1);
ctl |= NOT(sub(&t[d], &Curve.P, 0));
_ = sub(&t[d], &Curve.P, ctl);
},
2 => _ = add(&t[d], &Curve.P, sub(&t[d], &t[a], 1)),
3 => montymul(&t[d], &t[a], &t[b], &Curve.P, 1),
4 => {
var tp: [Curve.point_len / 2]u8 = undefined;
encode_jacobian_part(&tp, &Curve.P);
tp[Curve.point_len / 2 - 1] -= 2;
modpow(Curve, &t[d], tp, 1, &t[a], &t[b]);
},
else => result &= ~iszero(&t[d]),
}
}
P1.* = t[0..3].*;
return result;
}
fn MUL31(x: u32, y: u32) callconv(.Inline) u64 {
return @as(u64, x) * @as(u64, y);
}
fn MUL31_lo(x: u32, y: u32) callconv(.Inline) u32 {
return (x *% y) & 0x7FFFFFFF;
}
fn MUX(ctl: u32, x: u32, y: u32) callconv(.Inline) u32 {
return y ^ (@bitCast(u32, -@bitCast(i32, ctl)) & (x ^ y));
}
fn NOT(ctl: u32) callconv(.Inline) u32 {
return ctl ^ 1;
}
fn NEQ(x: u32, y: u32) callconv(.Inline) u32 {
const q = x ^ y;
return (q | @bitCast(u32, -@bitCast(i32, q))) >> 31;
}
fn EQ(x: u32, y: u32) callconv(.Inline) u32 {
const q = x ^ y;
return NOT((q | @bitCast(u32, -@bitCast(i32, q))) >> 31);
}
fn CMP(x: u32, y: u32) callconv(.Inline) i32 {
return @bitCast(i32, GT(x, y)) | -@bitCast(i32, GT(y, x));
}
fn GT(x: u32, y: u32) callconv(.Inline) u32 {
const z = y -% x;
return (z ^ ((x ^ y) & (x ^ z))) >> 31;
}
fn LT(x: u32, y: u32) callconv(.Inline) u32 {
return GT(y, x);
}
fn GE(x: u32, y: u32) callconv(.Inline) u32 {
return NOT(GT(y, x));
}
fn CCOPY(ctl: u32, dst: []u8, src: []const u8) void {
for (src) |s, i| {
dst[i] = @truncate(u8, MUX(ctl, s, dst[i]));
}
}
fn set_zero(out: [*]u32, bit_len: u32) callconv(.Inline) void {
out[0] = bit_len;
mem.set(u32, (out + 1)[0 .. (bit_len + 31) >> 5], 0);
}
fn divrem(_hi: u32, _lo: u32, d: u32, r: *u32) u32 {
var hi = _hi;
var lo = _lo;
var q: u32 = 0;
const ch = EQ(hi, d);
hi = MUX(ch, 0, hi);
var k: u5 = 31;
while (k > 0) : (k -= 1) {
const j = @truncate(u5, 32 - @as(u6, k));
const w = (hi << j) | (lo >> k);
const ctl = GE(w, d) | (hi >> k);
const hi2 = (w -% d) >> j;
const lo2 = lo -% (d << k);
hi = MUX(ctl, hi2, hi);
lo = MUX(ctl, lo2, lo);
q |= ctl << k;
}
const cf = GE(lo, d) | hi;
q |= cf;
r.* = MUX(cf, lo -% d, lo);
return q;
}
fn div(hi: u32, lo: u32, d: u32) callconv(.Inline) u32 {
var r: u32 = undefined;
return divrem(hi, lo, d, &r);
}
fn muladd_small(x: [*]u32, z: u32, m: [*]const u32) void {
var a0: u32 = undefined;
var a1: u32 = undefined;
var b0: u32 = undefined;
const mblr = @intCast(u5, m[0] & 31);
const mlen = (m[0] + 31) >> 5;
const hi = x[mlen];
if (mblr == 0) {
a0 = x[mlen];
mem.copyBackwards(u32, (x + 2)[0 .. mlen - 1], (x + 1)[0 .. mlen - 1]);
x[1] = z;
a1 = x[mlen];
b0 = m[mlen];
} else {
a0 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & 0x7FFFFFFF;
mem.copyBackwards(u32, (x + 2)[0 .. mlen - 1], (x + 1)[0 .. mlen - 1]);
x[1] = z;
a1 = ((x[mlen] << (31 - mblr)) | (x[mlen - 1] >> mblr)) & 0x7FFFFFFF;
b0 = ((m[mlen] << (31 - mblr)) | (m[mlen - 1] >> mblr)) & 0x7FFFFFFF;
}
const g = div(a0 >> 1, a1 | (a0 << 31), b0);
const q = MUX(EQ(a0, b0), 0x7FFFFFFF, MUX(EQ(g, 0), 0, g -% 1));
var cc: u32 = 0;
var tb: u32 = 1;
var u: usize = 1;
while (u <= mlen) : (u += 1) {
const mw = m[u];
const zl = MUL31(mw, q) + cc;
cc = @truncate(u32, zl >> 31);
const zw = @truncate(u32, zl) & 0x7FFFFFFF;
const xw = x[u];
var nxw = xw -% zw;
cc += nxw >> 31;
nxw &= 0x7FFFFFFF;
x[u] = nxw;
tb = MUX(EQ(nxw, mw), tb, GT(nxw, mw));
}
const over = GT(cc, hi);
const under = ~over & (tb | LT(cc, hi));
_ = add(x, m, over);
_ = sub(x, m, under);
}
fn to_monty(x: [*]u32, m: [*]const u32) void {
const mlen = (m[0] + 31) >> 5;
var k = mlen;
while (k > 0) : (k -= 1) {
muladd_small(x, 0, m);
}
}
fn modpow(
comptime Curve: type,
x: *[jacobian_len(Curve)]u32,
e: [Curve.point_len / 2]u8,
m0i: u32,
t1: *[jacobian_len(Curve)]u32,
t2: *[jacobian_len(Curve)]u32,
) void {
comptime const jaclen = jacobian_len(Curve);
t1.* = x.*;
to_monty(t1, &Curve.P);
set_zero(x, Curve.P[0]);
x[1] = 1;
comptime const bitlen = (Curve.point_len / 2) << 3;
var k: usize = 0;
while (k < bitlen) : (k += 1) {
const ctl = (e[Curve.point_len / 2 - 1 - (k >> 3)] >> (@truncate(u3, k & 7))) & 1;
montymul(t2, x, t1, &Curve.P, m0i);
CCOPY(ctl, mem.asBytes(x), mem.asBytes(t2));
montymul(t2, t1, t1, &Curve.P, m0i);
t1.* = t2.*;
}
}
fn encode_jacobian_part(dst: []u8, x: [*]const u32) void {
const xlen = (x[0] + 31) >> 5;
var buf = @ptrToInt(dst.ptr) + dst.len;
var len: usize = dst.len;
var k: usize = 1;
var acc: u32 = 0;
var acc_len: u5 = 0;
while (len != 0) {
const w = if (k <= xlen) x[k] else 0;
k += 1;
if (acc_len == 0) {
acc = w;
acc_len = 31;
} else {
const z = acc | (w << acc_len);
acc_len -= 1;
acc = w >> (31 - acc_len);
if (len >= 4) {
buf -= 4;
len -= 4;
mem.writeIntBig(u32, @intToPtr([*]u8, buf)[0..4], z);
} else {
switch (len) {
3 => {
@intToPtr(*u8, buf - 3).* = @truncate(u8, z >> 16);
@intToPtr(*u8, buf - 2).* = @truncate(u8, z >> 8);
},
2 => @intToPtr(*u8, buf - 2).* = @truncate(u8, z >> 8),
1 => {},
else => unreachable,
}
@intToPtr(*u8, buf - 1).* = @truncate(u8, z);
return;
}
}
}
}
fn montymul(
out: [*]u32,
x: [*]const u32,
y: [*]const u32,
m: [*]const u32,
m0i: u32,
) void {
const len = (m[0] + 31) >> 5;
const len4 = len & ~@as(usize, 3);
set_zero(out, m[0]);
var dh: u32 = 0;
var u: usize = 0;
while (u < len) : (u += 1) {
const xu = x[u + 1];
const f = MUL31_lo(out[1] + MUL31_lo(x[u + 1], y[1]), m0i);
var r: u64 = 0;
var v: usize = 0;
while (v < len4) : (v += 4) {
comptime var j = 1;
inline while (j <= 4) : (j += 1) {
const z = out[v + j] +% MUL31(xu, y[v + j]) +% MUL31(f, m[v + j]) +% r;
r = z >> 31;
out[v + j - 1] = @truncate(u32, z) & 0x7FFFFFFF;
}
}
while (v < len) : (v += 1) {
const z = out[v + 1] +% MUL31(xu, y[v + 1]) +% MUL31(f, m[v + 1]) +% r;
r = z >> 31;
out[v] = @truncate(u32, z) & 0x7FFFFFFF;
}
dh += @truncate(u32, r);
out[len] = dh & 0x7FFFFFFF;
dh >>= 31;
}
out[0] = m[0];
const ctl = NEQ(dh, 0) | NOT(sub(out, m, 0));
_ = sub(out, m, ctl);
}
fn add(a: [*]u32, b: [*]const u32, ctl: u32) u32 {
var u: usize = 1;
var cc: u32 = 0;
const m = (a[0] + 63) >> 5;
while (u < m) : (u += 1) {
const aw = a[u];
const bw = b[u];
const naw = aw +% bw +% cc;
cc = naw >> 31;
a[u] = MUX(ctl, naw & 0x7FFFFFFF, aw);
}
return cc;
}
fn sub(a: [*]u32, b: [*]const u32, ctl: u32) u32 {
var cc: u32 = 0;
const m = (a[0] + 63) >> 5;
var u: usize = 1;
while (u < m) : (u += 1) {
const aw = a[u];
const bw = b[u];
const naw = aw -% bw -% cc;
cc = naw >> 31;
a[u] = MUX(ctl, naw & 0x7FFFFFFF, aw);
}
return cc;
}
fn iszero(arr: [*]const u32) u32 {
const mlen = (arr[0] + 63) >> 5;
var z: u32 = 0;
var u: usize = mlen - 1;
while (u > 0) : (u -= 1) {
z |= arr[u];
}
return ~(z | @bitCast(u32, -@bitCast(i32, z))) >> 31;
}
};
test "elliptic curve functions with secp384r1 curve" {
{
// Decode to Jacobian then encode again with no operations
var P: ecc.Jacobian(ecc.SECP384R1) = undefined;
var res: u32 = ecc.decode_to_jacobian(ecc.SECP384R1, &P, ecc.SECP384R1.base_point);
var out: [96]u8 = undefined;
ecc.encode_from_jacobian(ecc.SECP384R1, &out, P);
try std.testing.expectEqual(ecc.SECP384R1.base_point, out);
// Multiply by one, check that the result is still the base point
mem.set(u8, &out, 0);
ecc.point_mul(ecc.SECP384R1, &P, &[1]u8{1});
ecc.encode_from_jacobian(ecc.SECP384R1, &out, P);
try std.testing.expectEqual(ecc.SECP384R1.base_point, out);
}
{
// @TODO Remove this once std.crypto.rand works in .evented mode
var rand = blk: {
var seed: [std.rand.DefaultCsprng.secret_seed_length]u8 = undefined;
try std.os.getrandom(&seed);
break :blk &std.rand.DefaultCsprng.init(seed).random;
};
// Derive a shared secret from a Diffie-Hellman key exchange
var seed: [48]u8 = undefined;
rand.bytes(&seed);
const kp1 = ecc.make_key_pair(ecc.SECP384R1, seed);
rand.bytes(&seed);
const kp2 = ecc.make_key_pair(ecc.SECP384R1, seed);
const shared1 = try ecc.scalarmult(ecc.SECP384R1, kp1.public_key, &kp2.secret_key);
const shared2 = try ecc.scalarmult(ecc.SECP384R1, kp2.public_key, &kp1.secret_key);
try std.testing.expectEqual(shared1, shared2);
}
// @TODO Add tests with known points.
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,209 @@
const std = @import("std");
const mem = std.mem;
const Allocator = mem.Allocator;
const Sha224 = std.crypto.hash.sha2.Sha224;
const Sha384 = std.crypto.hash.sha2.Sha384;
const Sha512 = std.crypto.hash.sha2.Sha512;
const Sha256 = std.crypto.hash.sha2.Sha256;
const x509 = @import("x509.zig");
const SignatureAlgorithm = x509.Certificate.SignatureAlgorithm;
const asn1 = @import("asn1.zig");
fn rsa_perform(
allocator: *Allocator,
modulus: std.math.big.int.Const,
exponent: std.math.big.int.Const,
base: []const u8,
) !?std.math.big.int.Managed {
// @TODO Better algorithm, make it faster.
const curr_base_limbs = try allocator.alloc(
usize,
std.math.divCeil(usize, base.len, @sizeOf(usize)) catch unreachable,
);
const curr_base_limb_bytes = @ptrCast([*]u8, curr_base_limbs)[0..base.len];
mem.copy(u8, curr_base_limb_bytes, base);
mem.reverse(u8, curr_base_limb_bytes);
var curr_base = (std.math.big.int.Mutable{
.limbs = curr_base_limbs,
.positive = true,
.len = curr_base_limbs.len,
}).toManaged(allocator);
defer curr_base.deinit();
var curr_exponent = try exponent.toManaged(allocator);
defer curr_exponent.deinit();
var result = try std.math.big.int.Managed.initSet(allocator, @as(usize, 1));
// encrypted = signature ^ key.exponent MOD key.modulus
while (curr_exponent.toConst().orderAgainstScalar(0) == .gt) {
if (curr_exponent.isOdd()) {
try result.ensureMulCapacity(result.toConst(), curr_base.toConst());
try result.mul(result.toConst(), curr_base.toConst());
try llmod(&result, modulus);
}
try curr_base.sqr(curr_base.toConst());
try llmod(&curr_base, modulus);
try curr_exponent.shiftRight(curr_exponent, 1);
}
if (result.limbs.len * @sizeOf(usize) < base.len)
return null;
return result;
}
// res = res mod N
fn llmod(res: *std.math.big.int.Managed, n: std.math.big.int.Const) !void {
var temp = try std.math.big.int.Managed.init(res.allocator);
defer temp.deinit();
try temp.divTrunc(res, res.toConst(), n);
}
pub fn algorithm_prefix(signature_algorithm: SignatureAlgorithm) ?[]const u8 {
return switch (signature_algorithm.hash) {
.none, .md5, .sha1 => null,
.sha224 => &[_]u8{
0x30, 0x2d, 0x30, 0x0d, 0x06,
0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x04,
0x05, 0x00, 0x04, 0x1c,
},
.sha256 => &[_]u8{
0x30, 0x31, 0x30, 0x0d, 0x06,
0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x01,
0x05, 0x00, 0x04, 0x20,
},
.sha384 => &[_]u8{
0x30, 0x41, 0x30, 0x0d, 0x06,
0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x02,
0x05, 0x00, 0x04, 0x30,
},
.sha512 => &[_]u8{
0x30, 0x51, 0x30, 0x0d, 0x06,
0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x03,
0x05, 0x00, 0x04, 0x40,
},
};
}
pub fn sign(
allocator: *Allocator,
signature_algorithm: SignatureAlgorithm,
hash: []const u8,
private_key: x509.PrivateKey,
) !?[]const u8 {
// @TODO ECDSA signatures
if (signature_algorithm.signature != .rsa or private_key != .rsa)
return null;
const signature_length = private_key.rsa.modulus.len * @sizeOf(usize);
var sig_buf = try allocator.alloc(u8, signature_length);
defer allocator.free(sig_buf);
const prefix = algorithm_prefix(signature_algorithm) orelse return null;
const first_prefix_idx = sig_buf.len - hash.len - prefix.len;
const first_hash_idx = sig_buf.len - hash.len;
// EM = 0x00 || 0x01 || PS || 0x00 || T
sig_buf[0] = 0;
sig_buf[1] = 1;
mem.set(u8, sig_buf[2 .. first_prefix_idx - 1], 0xff);
sig_buf[first_prefix_idx - 1] = 0;
mem.copy(u8, sig_buf[first_prefix_idx..first_hash_idx], prefix);
mem.copy(u8, sig_buf[first_hash_idx..], hash);
const modulus = std.math.big.int.Const{ .limbs = private_key.rsa.modulus, .positive = true };
const exponent = std.math.big.int.Const{ .limbs = private_key.rsa.exponent, .positive = true };
var rsa_result = (try rsa_perform(allocator, modulus, exponent, sig_buf)) orelse return null;
if (rsa_result.limbs.len * @sizeOf(usize) < signature_length) {
rsa_result.deinit();
return null;
}
const enc_buf = @ptrCast([*]u8, rsa_result.limbs.ptr)[0..signature_length];
mem.reverse(u8, enc_buf);
return allocator.resize(
enc_buf.ptr[0 .. rsa_result.limbs.len * @sizeOf(usize)],
signature_length,
) catch unreachable;
}
pub fn verify_signature(
allocator: *Allocator,
signature_algorithm: SignatureAlgorithm,
signature: asn1.BitString,
hash: []const u8,
public_key: x509.PublicKey,
) !bool {
// @TODO ECDSA algorithms
if (public_key != .rsa or signature_algorithm.signature != .rsa) return false;
const prefix = algorithm_prefix(signature_algorithm) orelse return false;
// RSA hash verification with PKCS 1 V1_5 padding
const modulus = std.math.big.int.Const{ .limbs = public_key.rsa.modulus, .positive = true };
const exponent = std.math.big.int.Const{ .limbs = public_key.rsa.exponent, .positive = true };
if (modulus.bitCountAbs() != signature.bit_len)
return false;
var rsa_result = (try rsa_perform(allocator, modulus, exponent, signature.data)) orelse return false;
defer rsa_result.deinit();
if (rsa_result.limbs.len * @sizeOf(usize) < signature.data.len)
return false;
const enc_buf = @ptrCast([*]u8, rsa_result.limbs.ptr)[0..signature.data.len];
mem.reverse(u8, enc_buf);
if (enc_buf[0] != 0x00 or enc_buf[1] != 0x01)
return false;
if (!mem.endsWith(u8, enc_buf, hash))
return false;
if (!mem.endsWith(u8, enc_buf[0 .. enc_buf.len - hash.len], prefix))
return false;
if (enc_buf[enc_buf.len - hash.len - prefix.len - 1] != 0x00)
return false;
for (enc_buf[2 .. enc_buf.len - hash.len - prefix.len - 1]) |c| {
if (c != 0xff) return false;
}
return true;
}
pub fn certificate_verify_signature(
allocator: *Allocator,
signature_algorithm: x509.Certificate.SignatureAlgorithm,
signature: asn1.BitString,
bytes: []const u8,
public_key: x509.PublicKey,
) !bool {
// @TODO ECDSA algorithms
if (public_key != .rsa or signature_algorithm.signature != .rsa) return false;
var hash_buf: [64]u8 = undefined;
var hash: []u8 = undefined;
switch (signature_algorithm.hash) {
// Deprecated hash algos
.none, .md5, .sha1 => return false,
.sha224 => {
Sha224.hash(bytes, hash_buf[0..28], .{});
hash = hash_buf[0..28];
},
.sha256 => {
Sha256.hash(bytes, hash_buf[0..32], .{});
hash = hash_buf[0..32];
},
.sha384 => {
Sha384.hash(bytes, hash_buf[0..48], .{});
hash = hash_buf[0..48];
},
.sha512 => {
Sha512.hash(bytes, hash_buf[0..64], .{});
hash = &hash_buf;
},
}
return try verify_signature(allocator, signature_algorithm, signature, hash, public_key);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----END CERTIFICATE-----

View file

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----

View file

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----

View file

@ -0,0 +1,61 @@
Bag Attributes
localKeyID: 41 C3 6C 33 C7 E3 36 DD EA 4A 1F C0 B7 23 B8 E6 9C DC D8 0F
subject=C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Client Certificate
issuer=C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Client Root Certificate Authority
-----BEGIN CERTIFICATE-----
MIIEqDCCApCgAwIBAgIUK5Ns4y2CzosB/ZoFlaxjZqoBTIIwDQYJKoZIhvcNAQEL
BQAwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
DVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkJhZFNTTDExMC8GA1UEAwwoQmFkU1NM
IENsaWVudCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xOTExMjcwMDE5
NTdaFw0yMTExMjYwMDE5NTdaMG8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp
Zm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKDAZCYWRTU0wx
IjAgBgNVBAMMGUJhZFNTTCBDbGllbnQgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDHN18R6x5Oz+u6SOXLoxIscz5GHR6cDcCLgyPa
x2XfXHdJs+h6fTy61WGM+aXEhR2SIwbj5997s34m0MsbvkJrFmn0LHK1fuTLCihE
EmxGdCGZA9xrwxFYAkEjP7D8v7cAWRMipYF/JP7VU7xNUo+QSkZ0sOi9k6bNkABK
L3+yP6PqAzsBoKIN5lN/YRLrppsDmk6nrRDo4R3CD+8JQl9quEoOmL22Pc/qpOjL
1jgOIFSE5y3gwbzDlfCYoAL5V+by1vu0yJShTTK8oo5wvphcFfEHaQ9w5jFg2htd
q99UER3BKuNDuL+zejqGQZCWb0Xsk8S5WBuX8l3Brrg5giqNAgMBAAGjLTArMAkG
A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgeAMAsGA1UdDwQEAwIF4DANBgkqhkiG
9w0BAQsFAAOCAgEAZBauLzFSOijkDadcippr9C6laHebb0oRS54xAV70E9k5GxfR
/E2EMuQ8X+miRUMXxKquffcDsSxzo2ac0flw94hDx3B6vJIYvsQx9Lzo95Im0DdT
DkHFXhTlv2kjQwFVnEsWYwyGpHMTjanvNkO7sBP9p1bN1qTE3QAeyMZNKWJk5xPl
U298ERar6tl3Z2Cl8mO6yLhrq4ba6iPGw08SENxzuAJW+n8r0rq7EU+bMg5spgT1
CxExzG8Bb0f98ZXMklpYFogkcuH4OUOFyRodotrotm3iRbuvZNk0Zz7N5n1oLTPl
bGPMwBcqaGXvK62NlaRkwjnbkPM4MYvREM0bbAgZD2GHyANBTso8bdWvhLvmoSjs
FSqJUJp17AZ0x/ELWZd69v2zKW9UdPmw0evyVR19elh/7dmtF6wbewc4N4jxQnTq
IItuhIWKWB9edgJz65uZ9ubQWjXoa+9CuWcV/1KxuKCbLHdZXiboLrKm4S1WmMYW
d0sJm95H9mJzcLyhLF7iX2kK6K9ug1y02YCVXBC9WGZc2x6GMS7lDkXSkJFy3EWh
CmfxkmFGwOgwKt3Jd1pF9ftcSEMhu4WcMgxi9vZr9OdkJLxmk033sVKI/hnkPaHw
g0Y2YBH5v0xmi8sYU7weOcwynkjZARpUltBUQ0pWCF5uJsEB8uE8PPDD3c4=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxzdfEeseTs/rukjly6MSLHM+Rh0enA3Ai4Mj2sdl31x3SbPo
en08utVhjPmlxIUdkiMG4+ffe7N+JtDLG75CaxZp9CxytX7kywooRBJsRnQhmQPc
a8MRWAJBIz+w/L+3AFkTIqWBfyT+1VO8TVKPkEpGdLDovZOmzZAASi9/sj+j6gM7
AaCiDeZTf2ES66abA5pOp60Q6OEdwg/vCUJfarhKDpi9tj3P6qToy9Y4DiBUhOct
4MG8w5XwmKAC+Vfm8tb7tMiUoU0yvKKOcL6YXBXxB2kPcOYxYNobXavfVBEdwSrj
Q7i/s3o6hkGQlm9F7JPEuVgbl/Jdwa64OYIqjQIDAQABAoIBAFUQf7fW/YoJnk5c
8kKRzyDL1Lt7k6Zu+NiZlqXEnutRQF5oQ8yJzXS5yH25296eOJI+AqMuT28ypZtN
bGzcQOAZIgTxNcnp9Sf9nlPyyekLjY0Y6PXaxX0e+VFj0N8bvbiYUGNq6HCyC15r
8uvRZRvnm04YfEj20zLTWkxTG+OwJ6ZNha1vfq8z7MG5JTsZbP0g7e/LrEb3wI7J
Zu9yHQUzq23HhfhpmLN/0l89YLtOaS8WNq4QvKYgZapw/0G1wWoWW4Y2/UpAxZ9r
cqTBWSpCSCCgyWjiNhPbSJWfe/9J2bcanITLcvCLlPWGAHy1wpo9iBH57y7S+7YS
3yi7lgECgYEA8lwaRIChc38tmtQCNPtai/7uVDdeJe0uv8Jsg04FTF8KMYcD0V1g
+T7rUPA+rTHwv8uAGLdzl4NW5Qryw18rDY+UivnaZkEdEsnlo3fc8MSQF78dDHCX
nwmHfOmBnBoSbLl+W5ByHkJRHOnX+8qKq9ePNFUMf/hZNYuma9BCFBUCgYEA0m2p
VDn12YdhFUUBIH91aD5cQIsBhkHFU4vqW4zBt6TsJpFciWbrBrTeRzeDou59aIsn
zGBrLMykOY+EwwRku9KTVM4U791Z/NFbH89GqyUaicb4or+BXw5rGF8DmzSsDo0f
ixJ9TVD5DmDi3c9ZQ7ljrtdSxPdA8kOoYPFsApkCgYEA08uZSPQAI6aoe/16UEK4
Rk9qhz47kHlNuVZ27ehoyOzlQ5Lxyy0HacmKaxkILOLPuUxljTQEWAv3DAIdVI7+
WMN41Fq0eVe9yIWXoNtGwUGFirsA77YVSm5RcN++3GQMZedUfUAl+juKFvJkRS4j
MTkXdGw+mDa3/wsjTGSa2mECgYABO6NCWxSVsbVf6oeXKSgG9FaWCjp4DuqZErjM
0IZSDSVVFIT2SSQXZffncuvSiJMziZ0yFV6LZKeRrsWYXu44K4Oxe4Oj5Cgi0xc1
mIFRf2YoaIIMchLP+8Wk3ummfyiC7VDB/9m8Gj1bWDX8FrrvKqbq31gcz1YSFVNn
PgLkAQKBgFzG8NdL8os55YcjBcOZMUs5QTKiQSyZM0Abab17k9JaqsU0jQtzeFsY
FTiwh2uh6l4gdO/dGC/P0Vrp7F05NnO7oE4T+ojDzVQMnFpCBeL7x08GfUQkphEG
m0Wqhhi8/24Sy934t5Txgkfoltg8ahkx934WjP6WWRnSAu+cf+vW
-----END RSA PRIVATE KEY-----

Binary file not shown.

View file

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----

View file

@ -0,0 +1,6 @@
id: csbnipaad8n77buaszsnjvlmn6j173fl7pkprsctelswjywe
name: iguanaTLS
main: src/main.zig
license: MIT
description: Minimal, experimental TLS 1.2 implementation in Zig
dependencies:

View file

@ -111,10 +111,10 @@ pub fn prettyPrintQVariantMap(qvariantmap: std.StringHashMap(QVariant), indentLe
var qMapIter = qvariantmap.iterator(); var qMapIter = qvariantmap.iterator();
while (qMapIter.next()) |v| { while (qMapIter.next()) |v| {
print_indent_level(indentLevel + 1); print_indent_level(indentLevel + 1);
std.debug.print("Key: \"{s}\"\n", .{v.key}); std.debug.print("Key: \"{s}\"\n", .{v.key_ptr.*});
print_indent_level(indentLevel + 1); print_indent_level(indentLevel + 1);
std.debug.print("Value:\n", .{}); std.debug.print("Value:\n", .{});
prettyPrintQVariant(v.value, indentLevel + 2); prettyPrintQVariant(v.value_ptr.*, indentLevel + 2);
} }
} }