aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/nkeys.zig262
-rw-r--r--src/znk.zig8
2 files changed, 234 insertions, 36 deletions
diff --git a/src/nkeys.zig b/src/nkeys.zig
index b66865d..9645483 100644
--- a/src/nkeys.zig
+++ b/src/nkeys.zig
@@ -95,7 +95,7 @@ pub const SeedKeyPair = struct {
95 Ed25519.verify(sig, msg, self.kp.public_key) catch return error.InvalidSignature; 95 Ed25519.verify(sig, msg, self.kp.public_key) catch return error.InvalidSignature;
96 } 96 }
97 97
98 pub fn textSeed(self: *const Self) text_seed { 98 pub fn seedText(self: *const Self) text_seed {
99 const full_prefix = &[_]u8{ 99 const full_prefix = &[_]u8{
100 @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(self.prefix) >> 5), 100 @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(self.prefix) >> 5),
101 (@enumToInt(self.prefix) & 0b00011111) << 3, 101 (@enumToInt(self.prefix) & 0b00011111) << 3,
@@ -104,11 +104,11 @@ pub const SeedKeyPair = struct {
104 return encode(full_prefix.len, seed.len, full_prefix, seed); 104 return encode(full_prefix.len, seed.len, full_prefix, seed);
105 } 105 }
106 106
107 pub fn textPrivateKey(self: *const Self) text_private { 107 pub fn privateKeyText(self: *const Self) text_private {
108 return encode(1, self.kp.secret_key.len, &[_]u8{@enumToInt(KeyTypePrefixByte.private)}, &self.kp.secret_key); 108 return encode(1, self.kp.secret_key.len, &[_]u8{@enumToInt(KeyTypePrefixByte.private)}, &self.kp.secret_key);
109 } 109 }
110 110
111 pub fn textPublicKey(self: *const Self) text_public { 111 pub fn publicKeyText(self: *const Self) text_public {
112 return encode(1, self.kp.public_key.len, &[_]u8{@enumToInt(self.prefix)}, &self.kp.public_key); 112 return encode(1, self.kp.public_key.len, &[_]u8{@enumToInt(self.prefix)}, &self.kp.public_key);
113 } 113 }
114 114
@@ -160,7 +160,7 @@ pub const PublicKey = struct {
160 return Self{ .prefix = prefix, .key = raw_key.* }; 160 return Self{ .prefix = prefix, .key = raw_key.* };
161 } 161 }
162 162
163 pub fn textPublicKey(self: *const Self) text_public { 163 pub fn publicKeyText(self: *const Self) text_public {
164 return encode(1, self.key.len, &[_]u8{@enumToInt(self.prefix)}, &self.key); 164 return encode(1, self.key.len, &[_]u8{@enumToInt(self.prefix)}, &self.key);
165 } 165 }
166 166
@@ -209,7 +209,7 @@ pub const PrivateKey = struct {
209 }; 209 };
210 } 210 }
211 211
212 pub fn textPrivateKey(self: *const Self) text_private { 212 pub fn privateKeyText(self: *const Self) text_private {
213 return encode(1, self.kp.secret_key.len, &[_]u8{@enumToInt(KeyTypePrefixByte.private)}, &self.kp.secret_key); 213 return encode(1, self.kp.secret_key.len, &[_]u8{@enumToInt(KeyTypePrefixByte.private)}, &self.kp.secret_key);
214 } 214 }
215 215
@@ -326,17 +326,25 @@ pub fn isValidEncoding(text: []const u8) bool {
326 return made_crc == got_crc; 326 return made_crc == got_crc;
327} 327}
328 328
329pub fn isValidSeed(text: *const text_seed) bool { 329pub fn isValidSeed(text: []const u8, with_type: ?PublicPrefixByte) bool {
330 var res = SeedKeyPair.fromTextSeed(text) catch return false; 330 if (text.len < text_seed_len) return false;
331 res.wipe(); 331 var res = SeedKeyPair.fromTextSeed(text[0..text_seed_len]) catch return false;
332 return true; 332 defer res.wipe();
333 return if (with_type) |ty| res.prefix == ty else true;
333} 334}
334 335
335pub fn isValidPublicKey(text: *const text_public, with_type: ?PublicPrefixByte) bool { 336pub fn isValidPublicKey(text: []const u8, with_type: ?PublicPrefixByte) bool {
336 var res = decode(1, Ed25519.public_length, text) catch return false; 337 if (text.len < text_public_len) return false;
338 var res = PublicKey.fromTextPublicKey(text[0..text_public_len]) catch return false;
337 defer res.wipe(); 339 defer res.wipe();
338 const public = PublicPrefixByte.fromU8(res.data[0]) catch return false; 340 return if (with_type) |ty| res.prefix == ty else true;
339 return if (with_type) |ty| public == ty else true; 341}
342
343pub fn isValidPrivateKey(text: []const u8) bool {
344 if (text.len < text_private_len) return false;
345 var res = PrivateKey.fromTextPrivateKey(text[0..text_private_len]) catch return false;
346 res.wipe();
347 return true;
340} 348}
341 349
342// `line` must not contain CR or LF characters. 350// `line` must not contain CR or LF characters.
@@ -420,7 +428,7 @@ pub fn parseDecoratedNkey(contents: []const u8) NoNkeySeedFoundError!SeedKeyPair
420 428
421pub fn parseDecoratedUserNkey(contents: []const u8) (NoNkeySeedFoundError || NoNkeyUserSeedFoundError)!SeedKeyPair { 429pub fn parseDecoratedUserNkey(contents: []const u8) (NoNkeySeedFoundError || NoNkeyUserSeedFoundError)!SeedKeyPair {
422 var key = try parseDecoratedNkey(contents); 430 var key = try parseDecoratedNkey(contents);
423 if (!mem.startsWith(u8, &key.textSeed(), "SU")) return error.NoNkeyUserSeedFound; 431 if (!mem.startsWith(u8, &key.seedText(), "SU")) return error.NoNkeyUserSeedFound;
424 defer key.wipe(); 432 defer key.wipe();
425 return key; 433 return key;
426} 434}
@@ -434,29 +442,219 @@ test {
434 442
435test { 443test {
436 var key_pair = try SeedKeyPair.generate(PublicPrefixByte.server); 444 var key_pair = try SeedKeyPair.generate(PublicPrefixByte.server);
437 defer key_pair.wipe(); 445 var decoded_seed = try SeedKeyPair.fromTextSeed(&key_pair.seedText());
438 446 try testing.expect(isValidEncoding(&decoded_seed.seedText()));
439 var decoded_seed = try SeedKeyPair.fromTextSeed(&key_pair.textSeed());
440 defer decoded_seed.wipe();
441 try testing.expect(isValidEncoding(&decoded_seed.textSeed()));
442 447
443 var pub_key_str_a = key_pair.textPublicKey(); 448 var pub_key_str_a = key_pair.publicKeyText();
444 defer wipeBytes(&pub_key_str_a); 449 var priv_key_str_a = key_pair.privateKeyText();
445 var priv_key_str = key_pair.textPrivateKey();
446 defer wipeBytes(&priv_key_str);
447 try testing.expect(pub_key_str_a.len != 0); 450 try testing.expect(pub_key_str_a.len != 0);
448 try testing.expect(priv_key_str.len != 0); 451 try testing.expect(priv_key_str_a.len != 0);
449 try testing.expect(isValidEncoding(&pub_key_str_a)); 452 try testing.expect(isValidEncoding(&pub_key_str_a));
450 try testing.expect(isValidEncoding(&priv_key_str)); 453 try testing.expect(isValidEncoding(&priv_key_str_a));
451 454
452 var pub_key = key_pair.intoPublicKey(); 455 var pub_key = key_pair.intoPublicKey();
453 defer pub_key.wipe(); 456 var pub_key_str_b = pub_key.publicKeyText();
454 var pub_key_str_b = pub_key.textPublicKey(); 457 try testing.expectEqualStrings(&pub_key_str_a, &pub_key_str_b);
455 defer wipeBytes(&pub_key_str_b); 458
456 try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b); 459 var priv_key = key_pair.intoPrivateKey();
460 var priv_key_str_b = priv_key.privateKeyText();
461 try testing.expectEqualStrings(&priv_key_str_a, &priv_key_str_b);
462}
463
464test "decode" {
465 const kp = try SeedKeyPair.generate(.account);
466 const seed_text = kp.seedText();
467 const pub_key_text = kp.publicKeyText();
468 const priv_key_text = kp.privateKeyText();
469
470 _ = try SeedKeyPair.fromTextSeed(&seed_text);
471 _ = try PublicKey.fromTextPublicKey(&pub_key_text);
472 _ = try PrivateKey.fromTextPrivateKey(&priv_key_text);
473
474 try testing.expectError(error.InvalidChecksum, PublicKey.fromTextPublicKey(seed_text[0..text_public_len]));
475 try testing.expectError(error.InvalidChecksum, SeedKeyPair.fromTextSeed(priv_key_text[0..text_seed_len]));
476}
477
478test "seed" {
479 inline for (@typeInfo(PublicPrefixByte).Enum.fields) |field| {
480 const prefix = @field(PublicPrefixByte, field.name);
481 const kp = try SeedKeyPair.generate(prefix);
482 const decoded = try SeedKeyPair.fromTextSeed(&kp.seedText());
483 if (decoded.prefix != prefix) {
484 std.debug.print("expected prefix {}, found prefix {}\n", .{ prefix, decoded.prefix });
485 return error.TestUnexpectedError;
486 }
487 }
488}
489
490test "public key" {
491 inline for (@typeInfo(PublicPrefixByte).Enum.fields) |field| {
492 const prefix = @field(PublicPrefixByte, field.name);
493 const kp = try SeedKeyPair.generate(prefix);
494 const decoded_pub_key = try PublicKey.fromTextPublicKey(&kp.publicKeyText());
495 if (decoded_pub_key.prefix != prefix) {
496 std.debug.print("expected prefix {}, found prefix {}\n", .{ prefix, decoded_pub_key.prefix });
497 return error.TestUnexpectedError;
498 }
499 }
500}
501
502test "account" {
503 const kp = try SeedKeyPair.generate(.account);
504 _ = try SeedKeyPair.fromTextSeed(&kp.seedText());
505
506 const pub_key_str = kp.publicKeyText();
507 try testing.expect(pub_key_str[0] == 'A');
508 try testing.expect(isValidPublicKey(&pub_key_str, .account));
509
510 const priv_key_str = kp.privateKeyText();
511 try testing.expect(priv_key_str[0] == 'P');
512 try testing.expect(isValidPrivateKey(&priv_key_str));
513
514 const data = "Hello, world!";
515 const sig = try kp.sign(data);
516 try testing.expect(sig.len == Ed25519.signature_length);
517 try kp.verify(data, sig);
518}
519
520test "cluster" {
521 const kp = try SeedKeyPair.generate(.cluster);
522
523 const pub_key_str = kp.publicKeyText();
524 try testing.expect(pub_key_str[0] == 'C');
525 try testing.expect(isValidPublicKey(&pub_key_str, .cluster));
526}
527
528test "operator" {
529 const kp = try SeedKeyPair.generate(.operator);
530
531 const pub_key_str = kp.publicKeyText();
532 try testing.expect(pub_key_str[0] == 'O');
533 try testing.expect(isValidPublicKey(&pub_key_str, .operator));
534}
535
536test "server" {
537 const kp = try SeedKeyPair.generate(.server);
538
539 const pub_key_str = kp.publicKeyText();
540 try testing.expect(pub_key_str[0] == 'N');
541 try testing.expect(isValidPublicKey(&pub_key_str, .server));
542}
543
544test "user" {
545 const kp = try SeedKeyPair.generate(.user);
546
547 const pub_key_str = kp.publicKeyText();
548 try testing.expect(pub_key_str[0] == 'U');
549 try testing.expect(isValidPublicKey(&pub_key_str, .user));
550}
551
552test "validation" {
553 const prefixes = @typeInfo(PublicPrefixByte).Enum.fields;
554 inline for (prefixes) |field, i| {
555 const prefix = @field(PublicPrefixByte, field.name);
556 const next_prefix = next: {
557 const next_field_i = if (i == prefixes.len - 1) 0 else i + 1;
558 std.debug.assert(next_field_i != i);
559 break :next @field(PublicPrefixByte, prefixes[next_field_i].name);
560 };
561 const kp = try SeedKeyPair.generate(prefix);
562
563 const seed_str = kp.seedText();
564 const pub_key_str = kp.publicKeyText();
565 const priv_key_str = kp.privateKeyText();
566
567 try testing.expect(isValidSeed(&seed_str, prefix));
568 try testing.expect(isValidSeed(&seed_str, null));
569 try testing.expect(isValidPublicKey(&pub_key_str, null));
570 try testing.expect(isValidPublicKey(&pub_key_str, prefix));
571 try testing.expect(isValidPrivateKey(&priv_key_str));
572
573 try testing.expect(!isValidSeed(&seed_str, next_prefix));
574 try testing.expect(!isValidSeed(&pub_key_str, null));
575 try testing.expect(!isValidSeed(&priv_key_str, null));
576 try testing.expect(!isValidPublicKey(&pub_key_str, next_prefix));
577 try testing.expect(!isValidPublicKey(&seed_str, null));
578 try testing.expect(!isValidPublicKey(&priv_key_str, null));
579 try testing.expect(!isValidPrivateKey(&seed_str));
580 try testing.expect(!isValidPrivateKey(&pub_key_str));
581 }
582
583 try testing.expect(!isValidSeed("seed", null));
584 try testing.expect(!isValidPublicKey("public key", null));
585 try testing.expect(!isValidPrivateKey("private key"));
586}
587
588test "from seed" {
589 const kp = try SeedKeyPair.generate(.account);
590 const kp_from_raw = try SeedKeyPair.fromRawSeed(kp.prefix, kp.kp.secret_key[0..Ed25519.seed_length]);
591 try testing.expect(std.meta.eql(kp, kp_from_raw));
592
593 const data = "Hello, World!";
594 const sig = try kp.sign(data);
595
596 const seed = kp.seedText();
597 try testing.expect(mem.startsWith(u8, &seed, "SA"));
598
599 const kp2 = try SeedKeyPair.fromTextSeed(&seed);
600 try kp2.verify(data, sig);
601}
602
603// TODO(rutgerbrf): give test a better name
604test "from public key" {
605 const kp = try SeedKeyPair.generate(.user);
606
607 const pk_text = kp.publicKeyText();
608 const pk_text_clone = kp.publicKeyText();
609 try testing.expectEqualStrings(&pk_text, &pk_text_clone);
610
611 const pk = try PublicKey.fromTextPublicKey(&pk_text);
612 const pk_text_clone_2 = pk.publicKeyText();
613 try testing.expect(std.meta.eql(pk, kp.intoPublicKey()));
614 try testing.expect(std.meta.eql(pk, PublicKey.fromRawPublicKey(kp.prefix, &kp.kp.public_key)));
615 try testing.expectEqualStrings(&pk_text, &pk_text_clone_2);
616
617 const data = "Hello, world!";
618
619 const sig = try kp.sign(data);
620 try pk.verify(data, sig);
621
622 // Create another user to sign and make sure verification fails
623 const kp2 = try SeedKeyPair.generate(.user);
624 const sig2 = try kp2.sign(data);
625
626 try testing.expectError(error.InvalidSignature, pk.verify(data, sig2));
627}
628
629test "from private key" {
630 const kp = try SeedKeyPair.generate(.account);
631
632 const pk_text = kp.privateKeyText();
633 const pk_text_clone = kp.privateKeyText();
634 try testing.expectEqualStrings(&pk_text, &pk_text_clone);
635
636 const pk = try PrivateKey.fromTextPrivateKey(&pk_text);
637 const pk_text_clone_2 = pk.privateKeyText();
638 try testing.expect(std.meta.eql(pk, kp.intoPrivateKey()));
639 try testing.expect(std.meta.eql(kp, pk.intoSeedKeyPair(.account)));
640 try testing.expect(std.meta.eql(pk, PrivateKey.fromRawPrivateKey(&kp.kp.secret_key)));
641 try testing.expectEqualStrings(&pk_text, &pk_text_clone_2);
642
643 const data = "Hello, World!";
644
645 const sig0 = try kp.sign(data);
646 const sig1 = try pk.sign(data);
647 try testing.expectEqualSlices(u8, &sig0, &sig1);
648 try pk.verify(data, sig0);
649 try kp.verify(data, sig1);
650
651 const kp2 = try SeedKeyPair.generate(.account);
652 const sig2 = try kp2.sign(data);
653
654 try testing.expectError(error.InvalidSignature, pk.verify(data, sig2));
457} 655}
458 656
459// TODO(rutgerbrf): test decode (+bad), seed, account, user, operator, cluster, isValid*, from*, fromRaw*, wipe 657// TODO(rutgerbrf): bad decode, wipe, sign, (public/private/seed) verify
460 658
461test "parse decorated JWT (bad)" { 659test "parse decorated JWT (bad)" {
462 try testing.expectEqualStrings("foo", parseDecoratedJwt("foo")); 660 try testing.expectEqualStrings("foo", parseDecoratedJwt("foo"));
@@ -486,10 +684,10 @@ test "parse decorated seed and JWT" {
486 const seed = "SUAGIEYODKBBTUMOB666Z5KA4FCWAZV7HWSGRHOD7MK6UM5IYLWLACH7DQ"; 684 const seed = "SUAGIEYODKBBTUMOB666Z5KA4FCWAZV7HWSGRHOD7MK6UM5IYLWLACH7DQ";
487 685
488 var got_kp = try parseDecoratedUserNkey(creds); 686 var got_kp = try parseDecoratedUserNkey(creds);
489 try testing.expectEqualStrings(seed, &got_kp.textSeed()); 687 try testing.expectEqualStrings(seed, &got_kp.seedText());
490 688
491 got_kp = try parseDecoratedNkey(creds); 689 got_kp = try parseDecoratedNkey(creds);
492 try testing.expectEqualStrings(seed, &got_kp.textSeed()); 690 try testing.expectEqualStrings(seed, &got_kp.seedText());
493 691
494 var got_jwt = parseDecoratedJwt(creds); 692 var got_jwt = parseDecoratedJwt(creds);
495 try testing.expectEqualStrings(jwt, got_jwt); 693 try testing.expectEqualStrings(jwt, got_jwt);
diff --git a/src/znk.zig b/src/znk.zig
index 7837cc8..4ab3077 100644
--- a/src/znk.zig
+++ b/src/znk.zig
@@ -149,10 +149,10 @@ pub fn cmdGen(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !voi
149 } else { 149 } else {
150 var kp = try nkeys.SeedKeyPair.generate(ty.?); 150 var kp = try nkeys.SeedKeyPair.generate(ty.?);
151 defer kp.wipe(); 151 defer kp.wipe();
152 try stdout.writeAll(&kp.textSeed()); 152 try stdout.writeAll(&kp.seedText());
153 try stdout.writeAll("\n"); 153 try stdout.writeAll("\n");
154 154
155 var public_key = kp.textPublicKey(); 155 var public_key = kp.publicKeyText();
156 if (pub_out) { 156 if (pub_out) {
157 try stdout.writeAll(&public_key); 157 try stdout.writeAll(&public_key);
158 try stdout.writeAll("\n"); 158 try stdout.writeAll("\n");
@@ -378,12 +378,12 @@ const PrefixKeyGenerator = struct {
378 378
379 var kp = try nkeys.SeedKeyPair.generate(self.ty); 379 var kp = try nkeys.SeedKeyPair.generate(self.ty);
380 defer kp.wipe(); 380 defer kp.wipe();
381 var public_key = kp.textPublicKey(); 381 var public_key = kp.publicKeyText();
382 if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue; 382 if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue;
383 383
384 if (self.done.xchg(true, .SeqCst)) return; // another thread is already done 384 if (self.done.xchg(true, .SeqCst)) return; // another thread is already done
385 385
386 info("{s}", .{kp.textSeed()}); 386 info("{s}", .{kp.seedText()});
387 info("{s}", .{public_key}); 387 info("{s}", .{public_key});
388 388
389 return; 389 return;