diff options
author | Rutger Broekhoff | 2021-05-25 14:38:24 +0200 |
---|---|---|
committer | Rutger Broekhoff | 2021-05-25 14:38:24 +0200 |
commit | 55b14e7fcfa3c340d27107f2b7cdf9836e7c4758 (patch) | |
tree | 76a8c7fd927ae599e26a711b0a0ef01b4c60c3d7 /src/nkeys.zig | |
parent | e9a14d7f019f264fed826a55294be14b2f37ae76 (diff) | |
download | zig-nkeys-55b14e7fcfa3c340d27107f2b7cdf9836e7c4758.tar.gz zig-nkeys-55b14e7fcfa3c340d27107f2b7cdf9836e7c4758.zip |
Write a few more tests, rename a few functions
Diffstat (limited to 'src/nkeys.zig')
-rw-r--r-- | src/nkeys.zig | 262 |
1 files changed, 230 insertions, 32 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 | ||
329 | pub fn isValidSeed(text: *const text_seed) bool { | 329 | pub 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 | ||
335 | pub fn isValidPublicKey(text: *const text_public, with_type: ?PublicPrefixByte) bool { | 336 | pub 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 | |||
343 | pub 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 | ||
421 | pub fn parseDecoratedUserNkey(contents: []const u8) (NoNkeySeedFoundError || NoNkeyUserSeedFoundError)!SeedKeyPair { | 429 | pub 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 | ||
435 | test { | 443 | test { |
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 | |||
464 | test "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 | |||
478 | test "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 | |||
490 | test "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 | |||
502 | test "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 | |||
520 | test "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 | |||
528 | test "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 | |||
536 | test "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 | |||
544 | test "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 | |||
552 | test "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 | |||
588 | test "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 | ||
604 | test "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 | |||
629 | test "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 | ||
461 | test "parse decorated JWT (bad)" { | 659 | test "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); |