split into client and main zig

This commit is contained in:
David Allemang
2024-03-05 13:38:13 -05:00
parent 24f32f5725
commit bd05dae216
4 changed files with 114 additions and 251 deletions

View File

@@ -28,36 +28,56 @@ pub fn build(b: *std.Build) void {
});
enet.addIncludePath(enet_real.path("include"));
const exe = b.addExecutable(.{
.name = "learnzig",
const client = b.addExecutable(.{
.name = "client",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("enet", enet);
client.root_module.addImport("enet", enet);
b.installArtifact(client);
b.installArtifact(exe);
const server = b.addExecutable(.{
.name = "server",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
server.root_module.addImport("enet", enet);
b.installArtifact(server);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
const client_cmd = b.addRunArtifact(client);
client_cmd.step.dependOn(b.getInstallStep());
const server_cmd = b.addRunArtifact(server);
server_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
client_cmd.addArgs(args);
server_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const client_step = b.step("client", "Run the client");
client_step.dependOn(&client_cmd.step);
const server_step = b.step("server", "Run the server");
server_step.dependOn(&server_cmd.step);
const exe_unit_tests = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
const client_tests = b.addTest(.{
.root_source_file = .{ .path = "src/client.zig" },
.target = target,
.optimize = optimize,
});
exe_unit_tests.root_module.addImport("enet", enet);
client.root_module.addImport("enet", enet);
const server_tests = b.addTest(.{
.root_source_file = .{ .path = "src/server.zig" },
.target = target,
.optimize = optimize,
});
server.root_module.addImport("enet", enet);
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
const run_client_tests = b.addRunArtifact(client_tests);
const run_server_tests = b.addRunArtifact(server_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_client_tests.step);
test_step.dependOn(&run_server_tests.step);
}

39
src/client.zig Normal file
View File

@@ -0,0 +1,39 @@
const std = @import("std");
const enet = @import("enet");
const DH = std.crypto.dh.X25519;
const AES = std.crypto.aead.aes_gcm.Aes256Gcm;
const SHA = std.crypto.hash.sha2.Sha256;
pub fn main() !void {
if (enet.enet_initialize() != 0) return error.ENetInitFailed;
defer enet.enet_deinitialize();
}
// it even works in ReleaseFast so I think it's ok.
test "in-place-encrypt" {
const al = std.testing.allocator;
var key: [AES.key_length]u8 = undefined;
std.crypto.random.bytes(&key);
const iv: [AES.nonce_length]u8 = undefined;
std.crypto.random.bytes(&key);
var tag: [AES.tag_length]u8 = undefined;
// 1mb message. very big. should find an error if one exists.
const original_message: []u8 = try al.alloc(u8, 1 << 20);
defer al.free(original_message);
std.crypto.random.bytes(original_message);
const message = try al.dupe(u8, original_message);
defer al.free(message);
const ad: []u8 = try al.dupe(u8, "u");
defer al.free(ad);
AES.encrypt(message, &tag, message, ad, iv, key);
try std.testing.expect(!std.mem.eql(u8, message, original_message));
try AES.decrypt(message, message, tag, ad, iv, key);
try std.testing.expect(std.mem.eql(u8, message, original_message));
}

View File

@@ -1,235 +0,0 @@
const std = @import("std");
const enet = @import("enet");
const DH = std.crypto.dh.X25519;
const AES = std.crypto.aead.aes_gcm.Aes256Gcm;
const SHA = std.crypto.hash.sha2.Sha256;
const Encryptor = struct {
const Self = @This();
kp: DH.KeyPair,
pub fn init() !Self {
var self: Self = undefined;
var seed: [DH.seed_length]u8 = undefined;
std.crypto.random.bytes(&seed);
self.kp = try DH.KeyPair.create(seed);
@memset(&seed, 0);
return self;
}
};
const Peer = struct {
const Self = @This();
public_key: [DH.public_length]u8,
shared_key: [AES.key_length]u8,
pub fn init(me: DH.KeyPair, peer_key: [DH.public_length]u8) !Self {
var self: Self = undefined;
self.public_key = peer_key;
const secret = try DH.scalarmult(me.secret_key, peer_key);
SHA.hash(&secret, &self.shared_key, .{});
return self;
}
/// Caller must free result
pub fn encrypt(self: Self, ally: std.mem.Allocator, msg: []const u8) !Packet {
var pkt: Packet = undefined;
std.crypto.random.bytes(&pkt.nonce);
pkt.cipher_text = try ally.alloc(u8, msg.len);
AES.encrypt(pkt.cipher_text, &pkt.tag, msg, "", pkt.nonce, self.shared_key);
return pkt;
}
/// Caller must free result
pub fn decrypt(self: Self, ally: std.mem.Allocator, pkt: Packet) ![]const u8 {
const msg: []u8 = try ally.alloc(u8, pkt.cipher_text.len);
try AES.decrypt(msg, pkt.cipher_text, pkt.tag, "", pkt.nonce, self.shared_key);
return msg;
}
};
const Packet = struct {
ally: std.mem.Allocator,
nonce: [AES.nonce_length]u8,
tag: [AES.tag_length]u8,
cipher_text: []u8,
};
pub fn main() !void {
if (enet.enet_initialize() != 0) return error.ENetInitFailed;
defer enet.enet_deinitialize();
const bob = try Encryptor.init();
const alice = try Encryptor.init();
std.debug.print(
"Bob sends {s}\n",
.{std.fmt.bytesToHex(bob.kp.public_key, .lower)},
);
std.debug.print(
"Alice sends {s}\n",
.{std.fmt.bytesToHex(alice.kp.public_key, .lower)},
);
const bob_peer = try Peer.init(bob.kp, alice.kp.public_key);
const alice_peer = try Peer.init(alice.kp, bob.kp.public_key);
std.debug.print(
"Bob deduces {s}\n",
.{std.fmt.bytesToHex(bob_peer.shared_key, .lower)},
);
std.debug.print(
"Alice deduces {s}\n",
.{std.fmt.bytesToHex(alice_peer.shared_key, .lower)},
);
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit();
const ally = arena.allocator();
// bob send a message to alice
const msg = "Hello World!";
std.debug.print(
"Bob sending message '{s}' ({s})\n",
.{ msg, std.fmt.bytesToHex(msg, .lower) },
);
const pkt = try bob_peer.encrypt(ally, msg);
defer ally.free(pkt.cipher_text);
std.debug.print(
"Packet: {s} {s} {s}\n",
.{
try digest(ally, pkt.cipher_text, .lower),
try digest(ally, &pkt.nonce, .lower),
try digest(ally, &pkt.tag, .lower),
},
);
std.debug.assert(!std.mem.eql(u8, pkt.cipher_text, msg));
const rcv = try alice_peer.decrypt(ally, pkt);
defer ally.free(rcv);
std.debug.print(
"Alice received '{s}' ({s})\n",
.{ rcv, try digest(ally, rcv, .lower) },
);
// var seed: [dh.X25519.seed_length]u8 = undefined;
// std.crypto.random.bytes(&seed);
// const kp1 = try dh.X25519.KeyPair.create(seed);
// std.crypto.random.bytes(&seed);
// const kp2 = try dh.X25519.KeyPair.create(seed);
//
// std.debug.print("{any}\n{any}\n\n", .{ kp1.public_key, kp1.secret_key });
// std.debug.print("{any}\n{any}\n\n", .{ kp2.public_key, kp2.secret_key });
//
// const sec_1 = try dh.X25519.scalarmult(kp1.secret_key, kp2.public_key);
// const sec_2 = try dh.X25519.scalarmult(kp2.secret_key, kp1.public_key);
//
// var key: [aead.aes_gcm.Aes256Gcm.key_length]u8 = undefined;
// std.crypto.hash.sha2.Sha256.hash(&sec_1, &key, .{});
//
// std.debug.print("{any}\n", .{sec_1});
// std.debug.print("{any}\n", .{sec_2});
//
// var gpa = std.heap.GeneralPurposeAllocator(.{}){};
// const ally = gpa.allocator();
//
// const m: []const u8 = "hello world!";
// const ad: []const u8 = "foo";
//
// // see https://crypto.stackexchange.com/a/5818
// // the nonce does not need to be random, it can be a counter
// // it can be sent in clear, or each end may deduce it
// // but it ___MUST___ be unique and unrelated to the key
// var iv: [aead.aes_gcm.Aes256Gcm.nonce_length]u8 = undefined;
// std.crypto.random.bytes(&iv);
//
// const c: []u8 = try ally.alloc(u8, m.len);
// defer ally.free(c);
//
// @memset(c, 0);
// var tag: [aead.aes_gcm.Aes256Gcm.tag_length]u8 = undefined;
//
// std.debug.print("c: {any}\n", .{c});
// aead.aes_gcm.Aes256Gcm.encrypt(c, &tag, m, ad, iv, key);
// std.debug.print("{any} {any} {any}\n", .{ c, tag, iv });
// aead.aes_gcm.Aes256Gcm.encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8)
// var seed_a = [sign.Ed25519.KeyPair.seed_length]u8;
// std.crypto.random.bytes(&seed_a);
// const pair_a = try sign.Ed25519.KeyPair.create(seed_a);
// var pubkey_a = dh.X25519.publicKeyFromEd25519(pair_a.public_key);
//
// var seed_b = [sign.Ed25519.KeyPair.seed_length]u8;
// std.crypto.random.bytes(&seed_b);
// const pair_b = try sign.Ed25519.KeyPair.create(seed_b);
// var pubkey_b = try dh.X25519.publicKeyFromEd25519(pair_b.public_key);
// var buf: [64]u8 = undefined;
// @memset(&buf, 0);
// std.debug.print("{any}\n", .{buf});
// std.crypto.random.bytes(&buf);
// std.debug.print("{any}\n", .{buf});
}
test "simple test" {
var list = std.ArrayList(i32).init(std.testing.allocator);
defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
try list.append(42);
try std.testing.expectEqual(@as(i32, 42), list.pop());
}
/// caller takes ownership of result
fn digest(ally: std.mem.Allocator, data: []const u8, case: std.fmt.Case) ![]const u8 {
const charset = switch (case) {
.lower => "0123456789abcdef",
.upper => "0123456789ABCDEF",
};
var res = try ally.alloc(u8, data.len * 2);
for (data, 0..) |ch, idx| {
res[idx * 2 + 0] = charset[ch >> 4];
res[idx * 2 + 1] = charset[ch & 0xf];
}
return res;
}
// it even works in ReleaseFast so I think it's ok.
test "in-place-encrypt" {
const al = std.testing.allocator;
var key: [AES.key_length]u8 = undefined;
std.crypto.random.bytes(&key);
const iv: [AES.nonce_length]u8 = undefined;
std.crypto.random.bytes(&key);
var tag: [AES.tag_length]u8 = undefined;
// 1mb message. very big. should find an error if one exists.
const original_message: []u8 = try al.alloc(u8, 1 << 20);
defer al.free(original_message);
std.crypto.random.bytes(original_message);
const message = try al.dupe(u8, original_message);
defer al.free(message);
const ad: []u8 = try al.dupe(u8, "u");
defer al.free(ad);
AES.encrypt(message, &tag, message, ad, iv, key);
try std.testing.expect(!std.mem.eql(u8, message, original_message));
try AES.decrypt(message, message, tag, ad, iv, key);
try std.testing.expect(std.mem.eql(u8, message, original_message));
}

39
src/server.zig Normal file
View File

@@ -0,0 +1,39 @@
const std = @import("std");
const enet = @import("enet");
const DH = std.crypto.dh.X25519;
const AES = std.crypto.aead.aes_gcm.Aes256Gcm;
const SHA = std.crypto.hash.sha2.Sha256;
pub fn main() !void {
if (enet.enet_initialize() != 0) return error.ENetInitFailed;
defer enet.enet_deinitialize();
}
// it even works in ReleaseFast so I think it's ok.
test "in-place-encrypt" {
const al = std.testing.allocator;
var key: [AES.key_length]u8 = undefined;
std.crypto.random.bytes(&key);
const iv: [AES.nonce_length]u8 = undefined;
std.crypto.random.bytes(&key);
var tag: [AES.tag_length]u8 = undefined;
// 1mb message. very big. should find an error if one exists.
const original_message: []u8 = try al.alloc(u8, 1 << 20);
defer al.free(original_message);
std.crypto.random.bytes(original_message);
const message = try al.dupe(u8, original_message);
defer al.free(message);
const ad: []u8 = try al.dupe(u8, "u");
defer al.free(ad);
AES.encrypt(message, &tag, message, ad, iv, key);
try std.testing.expect(!std.mem.eql(u8, message, original_message));
try AES.decrypt(message, message, tag, ad, iv, key);
try std.testing.expect(std.mem.eql(u8, message, original_message));
}