From bd05dae216ffde6acc713ed4df5305a7f065a433 Mon Sep 17 00:00:00 2001 From: David Allemang Date: Tue, 5 Mar 2024 13:38:13 -0500 Subject: [PATCH] split into client and main zig --- build.zig | 52 +++++++---- src/client.zig | 39 ++++++++ src/main.zig | 235 ------------------------------------------------- src/server.zig | 39 ++++++++ 4 files changed, 114 insertions(+), 251 deletions(-) create mode 100644 src/client.zig delete mode 100644 src/main.zig create mode 100644 src/server.zig diff --git a/build.zig b/build.zig index f4488e2..a08a760 100644 --- a/build.zig +++ b/build.zig @@ -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); } diff --git a/src/client.zig b/src/client.zig new file mode 100644 index 0000000..311c590 --- /dev/null +++ b/src/client.zig @@ -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)); +} diff --git a/src/main.zig b/src/main.zig deleted file mode 100644 index 1f30ffb..0000000 --- a/src/main.zig +++ /dev/null @@ -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)); -} diff --git a/src/server.zig b/src/server.zig new file mode 100644 index 0000000..311c590 --- /dev/null +++ b/src/server.zig @@ -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)); +}