aboutsummaryrefslogtreecommitdiffstats
path: root/tool
diff options
context:
space:
mode:
authorLibravatar Rutger Broekhoff2021-05-26 20:07:44 +0200
committerLibravatar Rutger Broekhoff2021-05-26 20:07:44 +0200
commit1956639e8a5f4f120c2600544ca579c834f08f29 (patch)
tree10125b048831e71d51afbe2f68b8f00edad88722 /tool
parentb52b1caf6d668340ecd47baf7b994c9f090484fc (diff)
downloadzig-nkeys-1956639e8a5f4f120c2600544ca579c834f08f29.tar.gz
zig-nkeys-1956639e8a5f4f120c2600544ca579c834f08f29.zip
Allow generating seeds with custom entropy
Diffstat (limited to 'tool')
-rw-r--r--tool/znk.zig156
1 files changed, 97 insertions, 59 deletions
diff --git a/tool/znk.zig b/tool/znk.zig
index 24a8ca4..974a620 100644
--- a/tool/znk.zig
+++ b/tool/znk.zig
@@ -90,17 +90,20 @@ const usage_gen =
90 \\ 90 \\
91 \\Generate Options: 91 \\Generate Options:
92 \\ 92 \\
93 \\ -e, --entropy Path of file to get entropy from
93 \\ -o, --pub-out Print the public key to stdout 94 \\ -o, --pub-out Print the public key to stdout
94 \\ -p, --prefix Vanity public key prefix, turns -o on 95 \\ -p, --prefix Vanity public key prefix, turns -o on
95 \\ 96 \\
96; 97;
97 98
98pub fn cmdGen(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !void { 99pub fn cmdGen(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !void {
100 const stdin = io.getStdIn();
99 const stdout = io.getStdOut(); 101 const stdout = io.getStdOut();
100 102
101 var role: ?nkeys.Role = null; 103 var role: ?nkeys.Role = null;
102 var pub_out: bool = false; 104 var pub_out: bool = false;
103 var prefix: ?[]const u8 = null; 105 var prefix: ?[]const u8 = null;
106 var entropy: ?fs.File = null;
104 107
105 var i: usize = 0; 108 var i: usize = 0;
106 while (i < args.len) : (i += 1) { 109 while (i < args.len) : (i += 1) {
@@ -117,6 +120,17 @@ pub fn cmdGen(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !voi
117 if (args[i].len > nkeys.text_public_len - 1) 120 if (args[i].len > nkeys.text_public_len - 1)
118 fatal("public key prefix '{s}' is too long", .{arg}); 121 fatal("public key prefix '{s}' is too long", .{arg});
119 prefix = args[i]; 122 prefix = args[i];
123 } else if (mem.eql(u8, arg, "-e") or mem.eql(u8, arg, "--entropy")) {
124 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
125 i += 1;
126 if (entropy != null) fatal("parameter '{s}' provided more than once", .{arg});
127 if (std.mem.eql(u8, args[i], "-")) {
128 entropy = stdin;
129 } else {
130 entropy = fs.cwd().openFile(args[i], .{}) catch {
131 fatal("could not open entropy file at {s}", .{args[i]});
132 };
133 }
120 } else { 134 } else {
121 fatal("unrecognized parameter: '{s}'", .{arg}); 135 fatal("unrecognized parameter: '{s}'", .{arg});
122 } 136 }
@@ -145,9 +159,22 @@ pub fn cmdGen(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !voi
145 if (prefix != null) { 159 if (prefix != null) {
146 const capitalized_prefix = try toUpper(arena, prefix.?); 160 const capitalized_prefix = try toUpper(arena, prefix.?);
147 161
148 try PrefixKeyGenerator.init(arena, role.?, capitalized_prefix).generate(); 162 const entropy_reader = if (entropy) |e| e.reader() else null;
163 const Generator = PrefixKeyGenerator(@TypeOf(entropy_reader.?));
164 var generator = Generator.init(arena, role.?, capitalized_prefix, entropy_reader);
165 generator.generate() catch {
166 fatal("failed to generate key", .{});
167 };
149 } else { 168 } else {
150 var kp = try nkeys.SeedKeyPair.generate(role.?); 169 var gen_result = res: {
170 if (entropy) |e| {
171 break :res nkeys.SeedKeyPair.generateWithCustomEntropy(role.?, e.reader());
172 } else {
173 break :res nkeys.SeedKeyPair.generate(role.?);
174 }
175 };
176 var kp = gen_result catch fatal("could not generate seed", .{});
177
151 defer kp.wipe(); 178 defer kp.wipe();
152 try stdout.writeAll(&kp.seedText()); 179 try stdout.writeAll(&kp.seedText());
153 try stdout.writeAll("\n"); 180 try stdout.writeAll("\n");
@@ -174,7 +201,6 @@ const usage_sign =
174; 201;
175 202
176pub fn cmdSign(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !void { 203pub fn cmdSign(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !void {
177 // TODO(rutgerbrf): support setting a custom entropy file?
178 const stdin = io.getStdIn(); 204 const stdin = io.getStdIn();
179 const stdout = io.getStdOut(); 205 const stdout = io.getStdOut();
180 206
@@ -199,7 +225,9 @@ pub fn cmdSign(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !vo
199 key = stdin; 225 key = stdin;
200 key_stdin = true; 226 key_stdin = true;
201 } else { 227 } else {
202 key = try fs.cwd().openFile(args[i], .{}); 228 key = fs.cwd().openFile(args[i], .{}) catch {
229 fatal("could not open key file at {s}", .{args[i]});
230 };
203 } 231 }
204 } else { 232 } else {
205 fatal("unrecognized parameter: '{s}'", .{arg}); 233 fatal("unrecognized parameter: '{s}'", .{arg});
@@ -210,7 +238,9 @@ pub fn cmdSign(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !vo
210 file = stdin; 238 file = stdin;
211 file_stdin = true; 239 file_stdin = true;
212 } else { 240 } else {
213 file = try fs.cwd().openFile(args[i], .{}); 241 file = fs.cwd().openFile(args[i], .{}) catch {
242 fatal("could not open file to generate signature for (at {s})", .{args[i]});
243 };
214 } 244 }
215 } 245 }
216 246
@@ -284,7 +314,9 @@ pub fn cmdVerify(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !
284 key = stdin; 314 key = stdin;
285 key_stdin = true; 315 key_stdin = true;
286 } else { 316 } else {
287 key = try fs.cwd().openFile(args[i], .{}); 317 key = fs.cwd().openFile(args[i], .{}) catch {
318 fatal("could not open file of key to verify with (at {s})", .{args[i]});
319 };
288 } 320 }
289 } else if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--sig")) { 321 } else if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--sig")) {
290 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); 322 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
@@ -294,7 +326,9 @@ pub fn cmdVerify(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !
294 sig = stdin; 326 sig = stdin;
295 sig_stdin = true; 327 sig_stdin = true;
296 } else { 328 } else {
297 sig = try fs.cwd().openFile(args[i], .{}); 329 sig = fs.cwd().openFile(args[i], .{}) catch {
330 fatal("could not open signature file at {s}", .{args[i]});
331 };
298 } 332 }
299 } else { 333 } else {
300 fatal("unrecognized parameter: '{s}'", .{arg}); 334 fatal("unrecognized parameter: '{s}'", .{arg});
@@ -305,7 +339,9 @@ pub fn cmdVerify(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !
305 file = stdin; 339 file = stdin;
306 file_stdin = true; 340 file_stdin = true;
307 } else { 341 } else {
308 file = try fs.cwd().openFile(args[i], .{}); 342 file = fs.cwd().openFile(args[i], .{}) catch {
343 fatal("could not open file to verify signature of (at {s})", .{args[i]});
344 };
309 } 345 }
310 } 346 }
311 347
@@ -324,7 +360,7 @@ pub fn cmdVerify(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !
324 fatal("no file to generate a signature for provided", .{}); 360 fatal("no file to generate a signature for provided", .{});
325 } 361 }
326 362
327 if (two(&.{ file_stdin, key_stdin, sig_stdin })) { 363 if ((file_stdin and key_stdin) or (file_stdin and sig_stdin) or (key_stdin and sig_stdin)) {
328 fatal("can't use stdin for reading multiple files", .{}); 364 fatal("can't use stdin for reading multiple files", .{});
329 } 365 }
330 366
@@ -355,64 +391,66 @@ pub fn cmdVerify(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !
355 try stdout.writeAll("good signature\n"); 391 try stdout.writeAll("good signature\n");
356} 392}
357 393
358const PrefixKeyGenerator = struct { 394fn PrefixKeyGenerator(comptime EntropyReaderType: type) type {
359 role: nkeys.Role, 395 return struct {
360 prefix: []const u8, 396 role: nkeys.Role,
361 allocator: *Allocator, 397 prefix: []const u8,
362 done: std.atomic.Bool, 398 allocator: *Allocator,
363 399 done: std.atomic.Bool,
364 const Self = @This(); 400 entropy: ?EntropyReaderType,
365 401
366 pub fn init(allocator: *Allocator, role: nkeys.Role, prefix: []const u8) Self { 402 const Self = @This();
367 return .{ 403
368 .role = role, 404 pub fn init(allocator: *Allocator, role: nkeys.Role, prefix: []const u8, entropy: ?EntropyReaderType) Self {
369 .prefix = prefix, 405 return .{
370 .allocator = allocator, 406 .role = role,
371 .done = std.atomic.Bool.init(false), 407 .prefix = prefix,
372 }; 408 .allocator = allocator,
373 } 409 .done = std.atomic.Bool.init(false),
410 .entropy = entropy,
411 };
412 }
374 413
375 fn generatePrivate(self: *Self) !void { 414 fn generatePrivate(self: *Self) !void {
376 while (true) { 415 while (true) {
377 if (self.done.load(.SeqCst)) return; 416 if (self.done.load(.SeqCst)) return;
378 417
379 var kp = try nkeys.SeedKeyPair.generate(self.role); 418 var gen_result = res: {
380 defer kp.wipe(); 419 if (self.entropy) |entropy| {
381 var public_key = kp.publicKeyText(); 420 break :res nkeys.SeedKeyPair.generateWithCustomEntropy(self.role, entropy);
382 if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue; 421 } else {
422 break :res nkeys.SeedKeyPair.generate(self.role);
423 }
424 };
425 var kp = gen_result catch fatal("could not generate seed", .{});
383 426
384 if (self.done.xchg(true, .SeqCst)) return; // another thread is already done 427 defer kp.wipe();
428 var public_key = kp.publicKeyText();
429 if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue;
385 430
386 info("{s}", .{kp.seedText()}); 431 if (self.done.xchg(true, .SeqCst)) return; // another thread is already done
387 info("{s}", .{public_key});
388 432
389 return; 433 info("{s}", .{kp.seedText()});
390 } 434 info("{s}", .{public_key});
391 }
392 435
393 pub usingnamespace if (builtin.single_threaded) struct { 436 return;
394 pub fn generate(self: *Self) !void { 437 }
395 return self.generatePrivate();
396 }
397 } else struct {
398 pub fn generate(self: *Self) !void {
399 var cpu_count = try std.Thread.cpuCount();
400 var threads = try self.allocator.alloc(*std.Thread, cpu_count);
401 defer self.allocator.free(threads);
402 for (threads) |*thread| thread.* = try std.Thread.spawn(Self.generatePrivate, self);
403 for (threads) |thread| thread.wait();
404 } 438 }
405 };
406};
407 439
408fn two(slice: []const bool) bool { 440 pub usingnamespace if (builtin.single_threaded) struct {
409 var one = false; 441 pub fn generate(self: *Self) !void {
410 for (slice) |x| if (x and one) { 442 return self.generatePrivate();
411 return true; 443 }
412 } else { 444 } else struct {
413 one = true; 445 pub fn generate(self: *Self) !void {
446 var cpu_count = try std.Thread.cpuCount();
447 var threads = try self.allocator.alloc(*std.Thread, cpu_count);
448 defer self.allocator.free(threads);
449 for (threads) |*thread| thread.* = try std.Thread.spawn(Self.generatePrivate, self);
450 for (threads) |thread| thread.wait();
451 }
452 };
414 }; 453 };
415 return false;
416} 454}
417 455
418fn toUpper(allocator: *Allocator, slice: []const u8) ![]u8 { 456fn toUpper(allocator: *Allocator, slice: []const u8) ![]u8 {
@@ -503,5 +541,5 @@ pub fn readKeyFile(allocator: *Allocator, file: fs.File) ?Nkey {
503test "reference all declarations" { 541test "reference all declarations" {
504 testing.refAllDecls(@This()); 542 testing.refAllDecls(@This());
505 testing.refAllDecls(Nkey); 543 testing.refAllDecls(Nkey);
506 testing.refAllDecls(PrefixKeyGenerator); 544 testing.refAllDecls(PrefixKeyGenerator(std.fs.File.Reader));
507} 545}