split into client and main zig
This commit is contained in:
52
build.zig
52
build.zig
@@ -28,36 +28,56 @@ pub fn build(b: *std.Build) void {
|
|||||||
});
|
});
|
||||||
enet.addIncludePath(enet_real.path("include"));
|
enet.addIncludePath(enet_real.path("include"));
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const client = b.addExecutable(.{
|
||||||
.name = "learnzig",
|
.name = "client",
|
||||||
.root_source_file = .{ .path = "src/main.zig" },
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.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);
|
const client_cmd = b.addRunArtifact(client);
|
||||||
|
client_cmd.step.dependOn(b.getInstallStep());
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
const server_cmd = b.addRunArtifact(server);
|
||||||
|
server_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
if (b.args) |args| {
|
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");
|
const client_step = b.step("client", "Run the client");
|
||||||
run_step.dependOn(&run_cmd.step);
|
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(.{
|
const client_tests = b.addTest(.{
|
||||||
.root_source_file = .{ .path = "src/main.zig" },
|
.root_source_file = .{ .path = "src/client.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.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");
|
const test_step = b.step("test", "Run tests");
|
||||||
test_step.dependOn(&run_exe_unit_tests.step);
|
test_step.dependOn(&run_client_tests.step);
|
||||||
|
test_step.dependOn(&run_server_tests.step);
|
||||||
}
|
}
|
||||||
|
39
src/client.zig
Normal file
39
src/client.zig
Normal 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));
|
||||||
|
}
|
235
src/main.zig
235
src/main.zig
@@ -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
39
src/server.zig
Normal 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));
|
||||||
|
}
|
Reference in New Issue
Block a user