diff options
-rw-r--r-- | src/base32.zig | 4 | ||||
-rw-r--r-- | src/nkeys.zig | 140 |
2 files changed, 90 insertions, 54 deletions
diff --git a/src/base32.zig b/src/base32.zig index 749c313..339566d 100644 --- a/src/base32.zig +++ b/src/base32.zig | |||
@@ -132,7 +132,7 @@ pub const Encoder = struct { | |||
132 | } | 132 | } |
133 | }; | 133 | }; |
134 | 134 | ||
135 | pub const DecodeError = error{CorruptInputError}; | 135 | pub const DecodeError = error{CorruptInput}; |
136 | 136 | ||
137 | pub const Decoder = struct { | 137 | pub const Decoder = struct { |
138 | const Self = @This(); | 138 | const Self = @This(); |
@@ -178,7 +178,7 @@ pub const Decoder = struct { | |||
178 | // '2' -> 26 | 178 | // '2' -> 26 |
179 | return @truncate(u5, c - @as(u8, '2') + 26); | 179 | return @truncate(u5, c - @as(u8, '2') + 26); |
180 | } else { | 180 | } else { |
181 | return error.CorruptInputError; | 181 | return error.CorruptInput; |
182 | } | 182 | } |
183 | } | 183 | } |
184 | 184 | ||
diff --git a/src/nkeys.zig b/src/nkeys.zig index 9645483..ac5ba1b 100644 --- a/src/nkeys.zig +++ b/src/nkeys.zig | |||
@@ -20,19 +20,37 @@ pub const PrivateKeyDecodeError = DecodeError || InvalidPrivateKeyError || crypt | |||
20 | pub const SignError = crypto.errors.IdentityElementError || crypto.errors.WeakPublicKeyError || crypto.errors.KeyMismatchError; | 20 | pub const SignError = crypto.errors.IdentityElementError || crypto.errors.WeakPublicKeyError || crypto.errors.KeyMismatchError; |
21 | 21 | ||
22 | pub const KeyTypePrefixByte = enum(u8) { | 22 | pub const KeyTypePrefixByte = enum(u8) { |
23 | const Self = @This(); | ||
24 | |||
23 | seed = 18 << 3, // S | 25 | seed = 18 << 3, // S |
24 | private = 15 << 3, // P | 26 | private = 15 << 3, // P |
25 | unknown = 23 << 3, // U | 27 | |
28 | fn char(self: Self) u8 { | ||
29 | switch (self) { | ||
30 | .seed => 'S', | ||
31 | .private => 'P', | ||
32 | } | ||
33 | } | ||
34 | |||
35 | fn fromChar(c: u8) InvalidPrefixByteError!Self { | ||
36 | return switch (c) { | ||
37 | 'S' => .seed, | ||
38 | 'P' => .private, | ||
39 | else => error.InvalidPrefixByte, | ||
40 | }; | ||
41 | } | ||
26 | }; | 42 | }; |
27 | 43 | ||
28 | pub const PublicPrefixByte = enum(u8) { | 44 | pub const PublicPrefixByte = enum(u8) { |
45 | const Self = @This(); | ||
46 | |||
29 | account = 0, // A | 47 | account = 0, // A |
30 | cluster = 2 << 3, // C | 48 | cluster = 2 << 3, // C |
31 | operator = 14 << 3, // O | 49 | operator = 14 << 3, // O |
32 | server = 13 << 3, // N | 50 | server = 13 << 3, // N |
33 | user = 20 << 3, // U | 51 | user = 20 << 3, // U |
34 | 52 | ||
35 | fn fromU8(b: u8) error{InvalidPrefixByte}!PublicPrefixByte { | 53 | fn fromU8(b: u8) InvalidPrefixByteError!PublicPrefixByte { |
36 | return switch (b) { | 54 | return switch (b) { |
37 | @enumToInt(PublicPrefixByte.server) => .server, | 55 | @enumToInt(PublicPrefixByte.server) => .server, |
38 | @enumToInt(PublicPrefixByte.cluster) => .cluster, | 56 | @enumToInt(PublicPrefixByte.cluster) => .cluster, |
@@ -42,6 +60,17 @@ pub const PublicPrefixByte = enum(u8) { | |||
42 | else => error.InvalidPrefixByte, | 60 | else => error.InvalidPrefixByte, |
43 | }; | 61 | }; |
44 | } | 62 | } |
63 | |||
64 | fn fromChar(c: u8) InvalidPrefixByteError!Self { | ||
65 | return switch (c) { | ||
66 | 'A' => .account, | ||
67 | 'C' => .cluster, | ||
68 | 'O' => .operator, | ||
69 | 'N' => .server, | ||
70 | 'U' => .user, | ||
71 | else => error.InvalidPrefixByte, | ||
72 | }; | ||
73 | } | ||
45 | }; | 74 | }; |
46 | 75 | ||
47 | pub const SeedKeyPair = struct { | 76 | pub const SeedKeyPair = struct { |
@@ -105,11 +134,11 @@ pub const SeedKeyPair = struct { | |||
105 | } | 134 | } |
106 | 135 | ||
107 | pub fn privateKeyText(self: *const Self) text_private { | 136 | 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); | 137 | return encode(1, self.kp.secret_key.len, &.{@enumToInt(KeyTypePrefixByte.private)}, &self.kp.secret_key); |
109 | } | 138 | } |
110 | 139 | ||
111 | pub fn publicKeyText(self: *const Self) text_public { | 140 | 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); | 141 | return encode(1, self.kp.public_key.len, &.{@enumToInt(self.prefix)}, &self.kp.public_key); |
113 | } | 142 | } |
114 | 143 | ||
115 | pub fn intoPublicKey(self: *const Self) PublicKey { | 144 | pub fn intoPublicKey(self: *const Self) PublicKey { |
@@ -161,7 +190,7 @@ pub const PublicKey = struct { | |||
161 | } | 190 | } |
162 | 191 | ||
163 | pub fn publicKeyText(self: *const Self) text_public { | 192 | pub fn publicKeyText(self: *const Self) text_public { |
164 | return encode(1, self.key.len, &[_]u8{@enumToInt(self.prefix)}, &self.key); | 193 | return encode(1, self.key.len, &.{@enumToInt(self.prefix)}, &self.key); |
165 | } | 194 | } |
166 | 195 | ||
167 | pub fn verify( | 196 | pub fn verify( |
@@ -210,7 +239,7 @@ pub const PrivateKey = struct { | |||
210 | } | 239 | } |
211 | 240 | ||
212 | pub fn privateKeyText(self: *const Self) text_private { | 241 | 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); | 242 | return encode(1, self.kp.secret_key.len, &.{@enumToInt(KeyTypePrefixByte.private)}, &self.kp.secret_key); |
214 | } | 243 | } |
215 | 244 | ||
216 | pub fn sign( | 245 | pub fn sign( |
@@ -499,54 +528,28 @@ test "public key" { | |||
499 | } | 528 | } |
500 | } | 529 | } |
501 | 530 | ||
502 | test "account" { | 531 | test "different key types" { |
503 | const kp = try SeedKeyPair.generate(.account); | 532 | inline for (@typeInfo(PublicPrefixByte).Enum.fields) |field| { |
504 | _ = try SeedKeyPair.fromTextSeed(&kp.seedText()); | 533 | const prefix = @field(PublicPrefixByte, field.name); |
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 | 534 | ||
536 | test "server" { | 535 | const kp = try SeedKeyPair.generate(prefix); |
537 | const kp = try SeedKeyPair.generate(.server); | 536 | _ = try SeedKeyPair.fromTextSeed(&kp.seedText()); |
538 | 537 | ||
539 | const pub_key_str = kp.publicKeyText(); | 538 | const pub_key_str = kp.publicKeyText(); |
540 | try testing.expect(pub_key_str[0] == 'N'); | 539 | const got_pub_key_prefix = try PublicPrefixByte.fromChar(pub_key_str[0]); |
541 | try testing.expect(isValidPublicKey(&pub_key_str, .server)); | 540 | try testing.expect(got_pub_key_prefix == prefix); |
542 | } | 541 | try testing.expect(isValidPublicKey(&pub_key_str, prefix)); |
543 | 542 | ||
544 | test "user" { | 543 | const priv_key_str = kp.privateKeyText(); |
545 | const kp = try SeedKeyPair.generate(.user); | 544 | const got_priv_key_prefix = try KeyTypePrefixByte.fromChar(priv_key_str[0]); |
545 | try testing.expect(got_priv_key_prefix == .private); | ||
546 | try testing.expect(isValidPrivateKey(&priv_key_str)); | ||
546 | 547 | ||
547 | const pub_key_str = kp.publicKeyText(); | 548 | const data = "Hello, world!"; |
548 | try testing.expect(pub_key_str[0] == 'U'); | 549 | const sig = try kp.sign(data); |
549 | try testing.expect(isValidPublicKey(&pub_key_str, .user)); | 550 | try testing.expect(sig.len == Ed25519.signature_length); |
551 | try kp.verify(data, sig); | ||
552 | } | ||
550 | } | 553 | } |
551 | 554 | ||
552 | test "validation" { | 555 | test "validation" { |
@@ -600,7 +603,6 @@ test "from seed" { | |||
600 | try kp2.verify(data, sig); | 603 | try kp2.verify(data, sig); |
601 | } | 604 | } |
602 | 605 | ||
603 | // TODO(rutgerbrf): give test a better name | ||
604 | test "from public key" { | 606 | test "from public key" { |
605 | const kp = try SeedKeyPair.generate(.user); | 607 | const kp = try SeedKeyPair.generate(.user); |
606 | 608 | ||
@@ -654,7 +656,41 @@ test "from private key" { | |||
654 | try testing.expectError(error.InvalidSignature, pk.verify(data, sig2)); | 656 | try testing.expectError(error.InvalidSignature, pk.verify(data, sig2)); |
655 | } | 657 | } |
656 | 658 | ||
657 | // TODO(rutgerbrf): bad decode, wipe, sign, (public/private/seed) verify | 659 | test "bad decode" { |
660 | const kp = try SeedKeyPair.fromTextSeed("SAAHPQF3GOP4IP5SHKHCNBOHD5TMGSW4QQL6RTZAPEEYOQ2NRBIAKCCLQA"); | ||
661 | |||
662 | var bad_seed = kp.seedText(); | ||
663 | bad_seed[1] = 'S'; | ||
664 | try testing.expectError(error.InvalidChecksum, SeedKeyPair.fromTextSeed(&bad_seed)); | ||
665 | |||
666 | var bad_pub_key = kp.publicKeyText(); | ||
667 | bad_pub_key[bad_pub_key.len - 1] = 'O'; | ||
668 | bad_pub_key[bad_pub_key.len - 2] = 'O'; | ||
669 | try testing.expectError(error.InvalidChecksum, PublicKey.fromTextPublicKey(&bad_pub_key)); | ||
670 | |||
671 | var bad_priv_key = kp.privateKeyText(); | ||
672 | bad_priv_key[bad_priv_key.len - 1] = 'O'; | ||
673 | bad_priv_key[bad_priv_key.len - 2] = 'O'; | ||
674 | try testing.expectError(error.InvalidChecksum, PrivateKey.fromTextPrivateKey(&bad_priv_key)); | ||
675 | } | ||
676 | |||
677 | test "wipe" { | ||
678 | const kp = try SeedKeyPair.generate(.account); | ||
679 | const pub_key = kp.intoPublicKey(); | ||
680 | const priv_key = kp.intoPrivateKey(); | ||
681 | |||
682 | var kp_clone = kp; | ||
683 | kp_clone.wipe(); | ||
684 | try testing.expect(!std.meta.eql(kp_clone.kp, kp.kp)); | ||
685 | |||
686 | var pub_key_clone = pub_key; | ||
687 | pub_key_clone.wipe(); | ||
688 | try testing.expect(!std.meta.eql(pub_key_clone.key, pub_key.key)); | ||
689 | |||
690 | var priv_key_clone = priv_key; | ||
691 | priv_key_clone.wipe(); | ||
692 | try testing.expect(!std.meta.eql(priv_key_clone.kp, priv_key.kp)); | ||
693 | } | ||
658 | 694 | ||
659 | test "parse decorated JWT (bad)" { | 695 | test "parse decorated JWT (bad)" { |
660 | try testing.expectEqualStrings("foo", parseDecoratedJwt("foo")); | 696 | try testing.expectEqualStrings("foo", parseDecoratedJwt("foo")); |