diff options
Diffstat (limited to 'src')
| -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")); |