From 07ac8dc00e7676d7845a881970c259679b0c5d23 Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Sat, 22 May 2021 21:19:53 +0200 Subject: Clean up `NKey` has been replaced with `Nkey` in all Zig source files, even though this capitalization is technically incorrect. The standard library also uses 'strict' camel/PascalCase everywhere, meaning that abbreviations like CRC, AES and GUID are spelled like Crc, Aes and Guid respectively. `var` has been replaced with `const` where applicable. Also, the `Key` type has been moved from src/nkeys.zig to src/znk.zig for now - it's still a little bit lacking and might not need to be included in the library. --- src/base32.zig | 14 ++--- src/nkeys.zig | 189 +++++++++++++++++++++------------------------------------ src/znk.zig | 93 ++++++++++++++++++++++------ 3 files changed, 151 insertions(+), 145 deletions(-) diff --git a/src/base32.zig b/src/base32.zig index d8adfd5..d612e7a 100644 --- a/src/base32.zig +++ b/src/base32.zig @@ -182,16 +182,14 @@ pub const Decoder = struct { /// Get a character from the buffer. fn decodeChar(c: u8) DecodeError!u5 { - var value: u5 = 0; if (c >= 'A' and c <= 'Z') { - value = @truncate(u5, c - @as(u8, 'A')); + return @truncate(u5, c - @as(u8, 'A')); } else if (c >= '2' and c <= '9') { // '2' -> 26 - value = @truncate(u5, c - @as(u8, '2') + 26); + return @truncate(u5, c - @as(u8, '2') + 26); } else { return error.CorruptInputError; } - return value; } /// Get the next 5-bit decoded character, read from `self.buffer`. @@ -248,12 +246,12 @@ test { const decoded = "this is a test"; var decode_buf: [Decoder.calcSize(encoded.len)]u8 = undefined; - var decode_res = try Decoder.decode(&decode_buf, encoded); + const decode_res = try Decoder.decode(&decode_buf, encoded); try testing.expectEqualStrings(decoded, decode_res); var encode_buf: [Encoder.calcSize(decoded.len)]u8 = undefined; - var encode_res = Encoder.encode(&encode_buf, decoded); + const encode_res = Encoder.encode(&encode_buf, decoded); try testing.expectEqualStrings(encoded, encode_res); } @@ -263,12 +261,12 @@ test { const decoded = &[_]u8{ 0x93, 0x40, 0x7f, 0x90, 0xfd, 0xbf, 0x1f, 0xd8, 0xe9, 0x9a, 0x89, 0x8b, 0xb5, 0xd4, 0x0b, 0xf3, 0x62, 0x54, 0x5d, 0x6d, 0xd1, 0x3d, 0xf7, 0x78, 0xad, 0x8d, 0x21, 0xc4, 0x8a, 0x01, 0x7a, 0xfd, 0xc3, 0x10, 0x2f, 0x5e }; var decode_buf: [Decoder.calcSize(encoded.len)]u8 = undefined; - var decode_res = try Decoder.decode(&decode_buf, encoded); + const decode_res = try Decoder.decode(&decode_buf, encoded); try testing.expectEqualSlices(u8, decoded, decode_res); var encode_buf: [Encoder.calcSize(decoded.len)]u8 = undefined; - var encode_res = Encoder.encode(&encode_buf, decoded); + const encode_res = Encoder.encode(&encode_buf, decoded); try testing.expectEqualSlices(u8, encoded, encode_res); } diff --git a/src/nkeys.zig b/src/nkeys.zig index 8806a81..1880fa8 100644 --- a/src/nkeys.zig +++ b/src/nkeys.zig @@ -11,63 +11,8 @@ const Error = error{ InvalidPrefixByte, InvalidEncoding, InvalidSeed, - NoNKeySeedFound, - NoNKeyUserSeedFound, -}; - -pub fn fromText(text: []const u8) !Key { - if (!isValidEncoding(text)) return error.InvalidEncoding; - switch (text[0]) { - 'S' => { - // It's a seed. - if (text.len != text_seed_len) return error.InvalidSeed; - return Key{ .seed_key_pair = try fromSeed(text[0..text_seed_len]) }; - }, - 'P' => return error.InvalidEncoding, // unsupported for now - else => { - if (text.len != text_public_len) return error.InvalidEncoding; - return Key{ .public_key = try fromPublicKey(text[0..text_public_len]) }; - }, - } -} - -pub const Key = union(enum) { - seed_key_pair: SeedKeyPair, - public_key: PublicKey, - - const Self = @This(); - - pub fn publicKey(self: *const Self) !text_public { - return switch (self.*) { - .seed_key_pair => |*kp| try kp.publicKey(), - .public_key => |*pk| try pk.publicKey(), - }; - } - - pub fn intoPublicKey(self: *const Self) !PublicKey { - return switch (self.*) { - .seed_key_pair => |*kp| try kp.intoPublicKey(), - .public_key => |pk| pk, - }; - } - - pub fn verify( - self: *const Self, - msg: []const u8, - sig: [Ed25519.signature_length]u8, - ) !void { - return switch (self.*) { - .seed_key_pair => |*kp| try kp.verify(msg, sig), - .public_key => |*pk| try pk.verify(msg, sig), - }; - } - - pub fn wipe(self: *Self) void { - return switch (self.*) { - .seed_key_pair => |*kp| kp.wipe(), - .public_key => |*pk| pk.wipe(), - }; - } + NoNkeySeedFound, + NoNkeyUserSeedFound, }; pub const KeyTypePrefixByte = enum(u8) { @@ -100,22 +45,24 @@ pub const SeedKeyPair = struct { seed: text_seed, - pub fn init(prefix: PublicPrefixByte) !Self { + pub fn generate(prefix: PublicPrefixByte) !Self { var raw_seed: [Ed25519.seed_length]u8 = undefined; crypto.random.bytes(&raw_seed); defer wipeBytes(&raw_seed); - var seed = try encodeSeed(prefix, &raw_seed); - return Self{ .seed = seed }; + return Self{ .seed = try encodeSeed(prefix, &raw_seed) }; } - pub fn initFromSeed(seed: *const text_seed) !Self { + pub fn fromTextSeed(seed: *const text_seed) !Self { var decoded = try decodeSeed(seed); - defer decoded.wipe(); - + decoded.wipe(); return Self{ .seed = seed.* }; } + pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) !Self { + return Self{ .seed = try encodeSeed(prefix, raw_seed) }; + } + fn rawSeed(self: *const Self) ![Ed25519.seed_length]u8 { return (try decodeSeed(&self.seed)).seed; } @@ -140,6 +87,7 @@ pub const SeedKeyPair = struct { pub fn intoPublicKey(self: *const Self) !PublicKey { var decoded = try decodeSeed(&self.seed); + defer decoded.wipe(); var kp = try Ed25519.KeyPair.create(decoded.seed); defer wipeKeyPair(&kp); return PublicKey{ @@ -186,6 +134,16 @@ pub const PublicKey = struct { prefix: PublicPrefixByte, key: [Ed25519.public_length]u8, + pub fn fromTextPublicKey(text: *const text_public) !PublicKey { + var decoded = try decode(1, Ed25519.public_length, text); + defer decoded.wipe(); // gets copied + + return PublicKey{ + .prefix = try PublicPrefixByte.fromU8(decoded.prefix[0]), + .key = decoded.data, + }; + } + pub fn publicKey(self: *const Self) !text_public { return try encodePublic(self.prefix, &self.key); } @@ -199,8 +157,8 @@ pub const PublicKey = struct { } pub fn wipe(self: *Self) void { - self.prefix = .user; - std.crypto.random.bytes(&self.key); + self.prefix = .account; + wipeBytes(&self.key); } }; @@ -247,22 +205,22 @@ fn encode( mem.writeIntLittle(u16, buf[buf.len - 2 .. buf.len], checksum); var text: encoded_key(prefix_len, data_len) = undefined; - _ = base32.Encoder.encode(&text, &buf); + std.debug.assert(base32.Encoder.encode(&text, &buf).len == text.len); return text; } pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) !text_seed { - var full_prefix = [_]u8{ + const full_prefix = &[_]u8{ @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(prefix) >> 5), (@enumToInt(prefix) & 0b00011111) << 3, }; - return encode(full_prefix.len, src.len, &full_prefix, src); + return encode(full_prefix.len, src.len, full_prefix, src); } pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 { var decoded = try decode(1, Ed25519.secret_length, text); - defer wipeBytes(&decoded.data); + defer decoded.wipe(); if (decoded.prefix[0] != @enumToInt(KeyTypePrefixByte.private)) return error.InvalidPrefixByte; return decoded.data; @@ -270,15 +228,23 @@ pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 { pub fn decodePublic(prefix: PublicPrefixByte, text: *const text_public) ![Ed25519.public_length]u8 { var decoded = try decode(1, Ed25519.public_length, text); + defer decoded.wipe(); if (decoded.data[0] != @enumToInt(prefix)) return error.InvalidPrefixByte; return decoded.data; } -fn DecodedNKey(comptime prefix_len: usize, comptime data_len: usize) type { +fn DecodedNkey(comptime prefix_len: usize, comptime data_len: usize) type { return struct { + const Self = @This(); + prefix: [prefix_len]u8, data: [data_len]u8, + + pub fn wipe(self: *Self) void { + self.prefix[0] = @enumToInt(PublicPrefixByte.account); + wipeBytes(&self.data); + } }; } @@ -286,15 +252,15 @@ fn decode( comptime prefix_len: usize, comptime data_len: usize, text: *const [base32.Encoder.calcSize(prefix_len + data_len + 2)]u8, -) !DecodedNKey(prefix_len, data_len) { +) !DecodedNkey(prefix_len, data_len) { var raw: [prefix_len + data_len + 2]u8 = undefined; defer wipeBytes(&raw); - _ = try base32.Decoder.decode(&raw, text[0..]); + std.debug.assert((try base32.Decoder.decode(&raw, text[0..])).len == raw.len); var checksum = mem.readIntLittle(u16, raw[raw.len - 2 .. raw.len]); try crc16.validate(raw[0 .. raw.len - 2], checksum); - return DecodedNKey(prefix_len, data_len){ + return DecodedNkey(prefix_len, data_len){ .prefix = raw[0..prefix_len].*, .data = raw[prefix_len .. raw.len - 2].*, }; @@ -314,7 +280,7 @@ pub const DecodedSeed = struct { pub fn decodeSeed(text: *const text_seed) !DecodedSeed { var decoded = try decode(2, Ed25519.seed_length, text); - defer wipeBytes(&decoded.data); // gets copied + defer decoded.wipe(); // gets copied var key_ty_prefix = decoded.prefix[0] & 0b11111000; var entity_ty_prefix = (decoded.prefix[0] & 0b00000111) << 5 | ((decoded.prefix[1] & 0b11111000) >> 3); @@ -328,22 +294,6 @@ pub fn decodeSeed(text: *const text_seed) !DecodedSeed { }; } -pub fn fromPublicKey(text: *const text_public) !PublicKey { - var decoded = try decode(1, Ed25519.public_length, text); - defer wipeBytes(&decoded.data); // gets copied - - return PublicKey{ - .prefix = try PublicPrefixByte.fromU8(decoded.prefix[0]), - .key = decoded.data, - }; -} - -pub fn fromSeed(text: *const text_seed) !SeedKeyPair { - var res = try decodeSeed(text); - wipeBytes(&res.seed); - return SeedKeyPair{ .seed = text.* }; -} - pub fn isValidEncoding(text: []const u8) bool { if (text.len < 4) return false; var made_crc: u16 = 0; @@ -367,24 +317,21 @@ pub fn isValidEncoding(text: []const u8) bool { pub fn isValidSeed(text: *const text_seed) bool { var res = decodeSeed(text) catch return false; - wipeBytes(&res.seed); + res.wipe(); return true; } pub fn isValidPublicKey(text: *const text_public, with_type: ?PublicPrefixByte) bool { var res = decode(1, Ed25519.public_length, text) catch return false; - var public = PublicPrefixByte.fromU8(res.data[0]) catch return false; + defer res.wipe(); + const public = PublicPrefixByte.fromU8(res.data[0]) catch return false; return if (with_type) |ty| public == ty else true; } -pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) !SeedKeyPair { - return SeedKeyPair{ .seed = try encodeSeed(prefix, raw_seed) }; -} - pub fn getNextLine(text: []const u8, off: *usize) ?[]const u8 { if (off.* >= text.len) return null; - var newline_pos = mem.indexOfPos(u8, text, off.*, "\n") orelse return null; - var start = off.*; + const newline_pos = mem.indexOfPos(u8, text, off.*, "\n") orelse return null; + const start = off.*; var end = newline_pos; if (newline_pos > 0 and text[newline_pos - 1] == '\r') end -= 1; off.* = newline_pos + 1; @@ -424,13 +371,13 @@ pub fn findKeySection(text: []const u8, off: *usize) ?[]const u8 { // TODO(rutgerbrf): switch to std.mem.SplitIterator while (true) { - var opening_line = getNextLine(text, off) orelse return null; + const opening_line = getNextLine(text, off) orelse return null; if (!isKeySectionBarrier(opening_line)) continue; - var contents_line = getNextLine(text, off) orelse return null; + const contents_line = getNextLine(text, off) orelse return null; if (!areKeySectionContentsValid(contents_line)) continue; - var closing_line = getNextLine(text, off) orelse return null; + const closing_line = getNextLine(text, off) orelse return null; if (!isKeySectionBarrier(closing_line)) continue; return contents_line; @@ -442,82 +389,86 @@ pub fn parseDecoratedJwt(contents: []const u8) ![]const u8 { return findKeySection(contents, ¤t_off) orelse return contents; } -fn validNKey(text: []const u8) bool { - var valid_prefix = +fn validNkey(text: []const u8) bool { + const valid_prefix = mem.startsWith(u8, text, "SO") or mem.startsWith(u8, text, "SA") or mem.startsWith(u8, text, "SU"); - var valid_len = text.len >= text_seed_len; + const valid_len = text.len >= text_seed_len; return valid_prefix and valid_len; } -fn findNKey(text: []const u8) ?[]const u8 { +fn findNkey(text: []const u8) ?[]const u8 { var current_off: usize = 0; while (true) { var line = getNextLine(text, ¤t_off) orelse return null; for (line) |c, i| { if (!ascii.isSpace(c)) { - if (validNKey(line[i..])) return line[i..]; + if (validNkey(line[i..])) return line[i..]; break; } } } } -pub fn parseDecoratedNKey(contents: []const u8) !SeedKeyPair { +pub fn parseDecoratedNkey(contents: []const u8) !SeedKeyPair { var current_off: usize = 0; - var seed: ?[]const u8 = null; if (findKeySection(contents, ¤t_off) != null) seed = findKeySection(contents, ¤t_off); if (seed == null) - seed = findNKey(contents) orelse return error.NoNKeySeedFound; - if (!validNKey(seed.?)) - return error.NoNKeySeedFound; - return fromSeed(seed.?[0..text_seed_len]); + seed = findNkey(contents) orelse return error.NoNkeySeedFound; + if (!validNkey(seed.?)) + return error.NoNkeySeedFound; + return SeedKeyPair.fromTextSeed(seed.?[0..text_seed_len]); } -pub fn parseDecoratedUserNKey(contents: []const u8) !SeedKeyPair { - var key = try parseDecoratedNKey(contents); - if (!mem.startsWith(u8, &key.seed, "SU")) return error.NoNKeyUserSeedFound; +pub fn parseDecoratedUserNkey(contents: []const u8) !SeedKeyPair { + var key = try parseDecoratedNkey(contents); + if (!mem.startsWith(u8, &key.seed, "SU")) return error.NoNkeyUserSeedFound; defer key.wipe(); return key; } test { testing.refAllDecls(@This()); - testing.refAllDecls(Key); testing.refAllDecls(SeedKeyPair); testing.refAllDecls(PublicKey); } test { - var key_pair = try SeedKeyPair.init(PublicPrefixByte.server); + var key_pair = try SeedKeyPair.generate(PublicPrefixByte.server); defer key_pair.wipe(); var decoded_seed = try decodeSeed(&key_pair.seed); + defer decoded_seed.wipe(); var encoded_second_time = try encodeSeed(decoded_seed.prefix, &decoded_seed.seed); + defer wipeBytes(&encoded_second_time); try testing.expectEqualSlices(u8, &key_pair.seed, &encoded_second_time); try testing.expect(isValidEncoding(&key_pair.seed)); var pub_key_str_a = try key_pair.publicKey(); + defer wipeBytes(&pub_key_str_a); var priv_key_str = try key_pair.privateKey(); + defer wipeBytes(&priv_key_str); try testing.expect(pub_key_str_a.len != 0); try testing.expect(priv_key_str.len != 0); try testing.expect(isValidEncoding(&pub_key_str_a)); try testing.expect(isValidEncoding(&priv_key_str)); - wipeBytes(&priv_key_str); var pub_key = try key_pair.intoPublicKey(); + defer pub_key.wipe(); var pub_key_str_b = try pub_key.publicKey(); + defer wipeBytes(&pub_key_str_b); try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b); } test { var creds_bytes = try std.fs.cwd().readFileAlloc(testing.allocator, "fixtures/test.creds", std.math.maxInt(usize)); defer testing.allocator.free(creds_bytes); + defer wipeBytes(creds_bytes); // TODO(rutgerbrf): validate the contents of the results of these functions - _ = try parseDecoratedUserNKey(creds_bytes); + _ = try parseDecoratedUserNkey(creds_bytes); _ = try parseDecoratedJwt(creds_bytes); } diff --git a/src/znk.zig b/src/znk.zig index ab36c96..fe66cb5 100644 --- a/src/znk.zig +++ b/src/znk.zig @@ -162,7 +162,7 @@ pub fn cmdGen(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !voi try PrefixKeyGenerator.init(arena, ty.?, capitalized_prefix).generate(); } else { - var kp = nkeys.SeedKeyPair.init(ty.?) catch |e| fatal("could not generate key pair: {e}", .{e}); + var kp = nkeys.SeedKeyPair.generate(ty.?) catch |e| fatal("could not generate key pair: {e}", .{e}); defer kp.wipe(); try stdout.writeAll(&kp.seed); try stdout.writeAll("\n"); @@ -396,7 +396,7 @@ const PrefixKeyGenerator = struct { while (true) { if (self.done.load(.SeqCst)) return; - var kp = nkeys.SeedKeyPair.init(self.ty) catch |e| fatal("could not generate key pair: {e}", .{e}); + var kp = nkeys.SeedKeyPair.generate(self.ty) catch |e| fatal("could not generate key pair: {e}", .{e}); defer kp.wipe(); var public_key = kp.publicKey() catch |e| fatal("could not generate public key: {e}", .{e}); if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue; @@ -425,22 +425,6 @@ const PrefixKeyGenerator = struct { }; }; -pub fn readKeyFile(allocator: *Allocator, file: fs.File) nkeys.Key { - var bytes = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch fatal("could not read key file", .{}); - - var iterator = mem.split(bytes, "\n"); - while (iterator.next()) |line| { - if (nkeys.isValidEncoding(line) and line.len == nkeys.text_seed_len) { - var k = nkeys.fromText(line) catch continue; - defer k.wipe(); - allocator.free(bytes); - return k; - } - } - - fatal("could not find a valid key", .{}); -} - fn two(slice: []const bool) bool { var one = false; for (slice) |x| if (x and one) { @@ -457,6 +441,79 @@ fn toUpper(allocator: *Allocator, slice: []const u8) ![]u8 { return result; } +pub const Nkey = union(enum) { + seed_key_pair: nkeys.SeedKeyPair, + public_key: nkeys.PublicKey, + + const Self = @This(); + + pub fn publicKey(self: *const Self) !nkeys.text_public { + return switch (self.*) { + .seed_key_pair => |*kp| try kp.publicKey(), + .public_key => |*pk| try pk.publicKey(), + }; + } + + pub fn intoPublicKey(self: *const Self) !nkeys.PublicKey { + return switch (self.*) { + .seed_key_pair => |*kp| try kp.intoPublicKey(), + .public_key => |pk| pk, + }; + } + + pub fn verify( + self: *const Self, + msg: []const u8, + sig: [std.crypto.sign.Ed25519.signature_length]u8, + ) !void { + return switch (self.*) { + .seed_key_pair => |*kp| try kp.verify(msg, sig), + .public_key => |*pk| try pk.verify(msg, sig), + }; + } + + pub fn wipe(self: *Self) void { + return switch (self.*) { + .seed_key_pair => |*kp| kp.wipe(), + .public_key => |*pk| pk.wipe(), + }; + } + + pub fn fromText(text: []const u8) !Self { + if (!nkeys.isValidEncoding(text)) return error.InvalidEncoding; + switch (text[0]) { + 'S' => { + // It's a seed. + if (text.len != nkeys.text_seed_len) return error.InvalidSeed; + return Self{ .seed_key_pair = try nkeys.SeedKeyPair.fromTextSeed(text[0..nkeys.text_seed_len]) }; + }, + 'P' => return error.InvalidEncoding, // unsupported for now + else => { + if (text.len != nkeys.text_public_len) return error.InvalidEncoding; + return Self{ .public_key = try nkeys.PublicKey.fromTextPublicKey(text[0..nkeys.text_public_len]) }; + }, + } + } +}; + +pub fn readKeyFile(allocator: *Allocator, file: fs.File) Nkey { + var bytes = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch fatal("could not read key file", .{}); + + var iterator = mem.split(bytes, "\n"); + while (iterator.next()) |line| { + if (nkeys.isValidEncoding(line) and line.len == nkeys.text_seed_len) { + var k = Nkey.fromText(line) catch continue; + defer k.wipe(); + allocator.free(bytes); + return k; + } + } + + fatal("could not find a valid key", .{}); +} + test { testing.refAllDecls(@This()); + testing.refAllDecls(Nkey); + testing.refAllDecls(PrefixKeyGenerator); } -- cgit v1.2.3