aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/crc16.zig4
-rw-r--r--src/nkeys.zig172
-rw-r--r--src/znk.zig19
3 files changed, 88 insertions, 107 deletions
diff --git a/src/crc16.zig b/src/crc16.zig
index b00c795..2c49500 100644
--- a/src/crc16.zig
+++ b/src/crc16.zig
@@ -1,4 +1,4 @@
1const Error = error{InvalidChecksum}; 1pub const InvalidChecksumError = error{InvalidChecksum};
2 2
3const crc16tab: [256]u16 = tab: { 3const crc16tab: [256]u16 = tab: {
4 @setEvalBranchQuota(10000); 4 @setEvalBranchQuota(10000);
@@ -36,6 +36,6 @@ pub fn make(data: []const u8) u16 {
36} 36}
37 37
38// validate will check the calculated CRC16 checksum for data against the expected. 38// validate will check the calculated CRC16 checksum for data against the expected.
39pub fn validate(data: []const u8, expected: u16) !void { 39pub fn validate(data: []const u8, expected: u16) InvalidChecksumError!void {
40 if (make(data) != expected) return error.InvalidChecksum; 40 if (make(data) != expected) return error.InvalidChecksum;
41} 41}
diff --git a/src/nkeys.zig b/src/nkeys.zig
index 1880fa8..e8410fd 100644
--- a/src/nkeys.zig
+++ b/src/nkeys.zig
@@ -7,13 +7,11 @@ const Ed25519 = crypto.sign.Ed25519;
7const mem = std.mem; 7const mem = std.mem;
8const testing = std.testing; 8const testing = std.testing;
9 9
10const Error = error{ 10pub const InvalidPrefixByteError = error{InvalidPrefixByte};
11 InvalidPrefixByte, 11pub const InvalidEncodingError = error{InvalidEncoding};
12 InvalidEncoding, 12pub const InvalidSeedError = error{InvalidSeed};
13 InvalidSeed, 13pub const NoNkeySeedFoundError = error{NoNkeySeedFound};
14 NoNkeySeedFound, 14pub const NoNkeyUserSeedFoundError = error{NoNkeyUserSeedFound};
15 NoNkeyUserSeedFound,
16};
17 15
18pub const KeyTypePrefixByte = enum(u8) { 16pub const KeyTypePrefixByte = enum(u8) {
19 seed = 18 << 3, // S 17 seed = 18 << 3, // S
@@ -28,7 +26,7 @@ pub const PublicPrefixByte = enum(u8) {
28 server = 13 << 3, // N 26 server = 13 << 3, // N
29 user = 20 << 3, // U 27 user = 20 << 3, // U
30 28
31 fn fromU8(b: u8) !PublicPrefixByte { 29 fn fromU8(b: u8) error{InvalidPrefixByte}!PublicPrefixByte {
32 return switch (b) { 30 return switch (b) {
33 @enumToInt(PublicPrefixByte.server) => .server, 31 @enumToInt(PublicPrefixByte.server) => .server,
34 @enumToInt(PublicPrefixByte.cluster) => .cluster, 32 @enumToInt(PublicPrefixByte.cluster) => .cluster,
@@ -45,47 +43,47 @@ pub const SeedKeyPair = struct {
45 43
46 seed: text_seed, 44 seed: text_seed,
47 45
48 pub fn generate(prefix: PublicPrefixByte) !Self { 46 pub fn generate(prefix: PublicPrefixByte) Self {
49 var raw_seed: [Ed25519.seed_length]u8 = undefined; 47 var raw_seed: [Ed25519.seed_length]u8 = undefined;
50 crypto.random.bytes(&raw_seed); 48 crypto.random.bytes(&raw_seed);
51 defer wipeBytes(&raw_seed); 49 defer wipeBytes(&raw_seed);
52 50
53 return Self{ .seed = try encodeSeed(prefix, &raw_seed) }; 51 return Self{ .seed = encodeSeed(prefix, &raw_seed) };
54 } 52 }
55 53
56 pub fn fromTextSeed(seed: *const text_seed) !Self { 54 pub fn fromTextSeed(seed: *const text_seed) SeedDecodeError!Self {
57 var decoded = try decodeSeed(seed); 55 var decoded = try decodeSeed(seed);
58 decoded.wipe(); 56 decoded.wipe();
59 return Self{ .seed = seed.* }; 57 return Self{ .seed = seed.* };
60 } 58 }
61 59
62 pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) !Self { 60 pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) Self {
63 return Self{ .seed = try encodeSeed(prefix, raw_seed) }; 61 return Self{ .seed = encodeSeed(prefix, raw_seed) };
64 } 62 }
65 63
66 fn rawSeed(self: *const Self) ![Ed25519.seed_length]u8 { 64 fn rawSeed(self: *const Self) SeedDecodeError![Ed25519.seed_length]u8 {
67 return (try decodeSeed(&self.seed)).seed; 65 return (try decodeSeed(&self.seed)).seed;
68 } 66 }
69 67
70 fn keys(self: *const Self) !Ed25519.KeyPair { 68 fn keys(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!Ed25519.KeyPair {
71 return Ed25519.KeyPair.create(try rawSeed(self)); 69 return Ed25519.KeyPair.create(try rawSeed(self));
72 } 70 }
73 71
74 pub fn privateKey(self: *const Self) !text_private { 72 pub fn privateKey(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!text_private {
75 var kp = try self.keys(); 73 var kp = try self.keys();
76 defer wipeKeyPair(&kp); 74 defer wipeKeyPair(&kp);
77 return try encodePrivate(&kp.secret_key); 75 return encodePrivate(&kp.secret_key);
78 } 76 }
79 77
80 pub fn publicKey(self: *const Self) !text_public { 78 pub fn publicKey(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!text_public {
81 var decoded = try decodeSeed(&self.seed); 79 var decoded = try decodeSeed(&self.seed);
82 defer decoded.wipe(); 80 defer decoded.wipe();
83 var kp = try Ed25519.KeyPair.create(decoded.seed); 81 var kp = try Ed25519.KeyPair.create(decoded.seed);
84 defer wipeKeyPair(&kp); 82 defer wipeKeyPair(&kp);
85 return try encodePublic(decoded.prefix, &kp.public_key); 83 return encodePublic(decoded.prefix, &kp.public_key);
86 } 84 }
87 85
88 pub fn intoPublicKey(self: *const Self) !PublicKey { 86 pub fn intoPublicKey(self: *const Self) (SeedDecodeError || crypto.errors.IdentityElementError)!PublicKey {
89 var decoded = try decodeSeed(&self.seed); 87 var decoded = try decodeSeed(&self.seed);
90 defer decoded.wipe(); 88 defer decoded.wipe();
91 var kp = try Ed25519.KeyPair.create(decoded.seed); 89 var kp = try Ed25519.KeyPair.create(decoded.seed);
@@ -96,13 +94,19 @@ pub const SeedKeyPair = struct {
96 }; 94 };
97 } 95 }
98 96
97 pub const SignError = SeedDecodeError || crypto.errors.IdentityElementError || crypto.errors.WeakPublicKeyError;
98
99 pub fn sign( 99 pub fn sign(
100 self: *const Self, 100 self: *const Self,
101 msg: []const u8, 101 msg: []const u8,
102 ) ![Ed25519.signature_length]u8 { 102 ) SignError![Ed25519.signature_length]u8 {
103 var kp = try self.keys(); 103 var kp = try self.keys();
104 defer wipeKeyPair(&kp); 104 defer wipeKeyPair(&kp);
105 return try Ed25519.sign(msg, kp, null); 105 return Ed25519.sign(msg, kp, null) catch |e| switch (e) {
106 error.KeyMismatch => unreachable, // would mean that self.keys() has an incorrect implementation
107 error.WeakPublicKey => error.WeakPublicKey,
108 error.IdentityElement => error.IdentityElement,
109 };
106 } 110 }
107 111
108 pub fn verify( 112 pub fn verify(
@@ -112,7 +116,7 @@ pub const SeedKeyPair = struct {
112 ) !void { 116 ) !void {
113 var kp = try self.keys(); 117 var kp = try self.keys();
114 defer wipeKeyPair(&kp); 118 defer wipeKeyPair(&kp);
115 try Ed25519.verify(sig, msg, kp.public_key); 119 Ed25519.verify(sig, msg, kp.public_key) catch return error.InvalidSignature;
116 } 120 }
117 121
118 pub fn wipe(self: *Self) void { 122 pub fn wipe(self: *Self) void {
@@ -134,18 +138,17 @@ pub const PublicKey = struct {
134 prefix: PublicPrefixByte, 138 prefix: PublicPrefixByte,
135 key: [Ed25519.public_length]u8, 139 key: [Ed25519.public_length]u8,
136 140
137 pub fn fromTextPublicKey(text: *const text_public) !PublicKey { 141 pub fn fromTextPublicKey(text: *const text_public) DecodeError!PublicKey {
138 var decoded = try decode(1, Ed25519.public_length, text); 142 var decoded = try decode(1, Ed25519.public_length, text);
139 defer decoded.wipe(); // gets copied 143 defer decoded.wipe(); // gets copied
140
141 return PublicKey{ 144 return PublicKey{
142 .prefix = try PublicPrefixByte.fromU8(decoded.prefix[0]), 145 .prefix = try PublicPrefixByte.fromU8(decoded.prefix[0]),
143 .key = decoded.data, 146 .key = decoded.data,
144 }; 147 };
145 } 148 }
146 149
147 pub fn publicKey(self: *const Self) !text_public { 150 pub fn publicKey(self: *const Self) text_public {
148 return try encodePublic(self.prefix, &self.key); 151 return encodePublic(self.prefix, &self.key);
149 } 152 }
150 153
151 pub fn verify( 154 pub fn verify(
@@ -153,7 +156,7 @@ pub const PublicKey = struct {
153 msg: []const u8, 156 msg: []const u8,
154 sig: [Ed25519.signature_length]u8, 157 sig: [Ed25519.signature_length]u8,
155 ) !void { 158 ) !void {
156 try Ed25519.verify(sig, msg, self.key); 159 Ed25519.verify(sig, msg, self.key) catch return error.InvalidSignature;
157 } 160 }
158 161
159 pub fn wipe(self: *Self) void { 162 pub fn wipe(self: *Self) void {
@@ -177,11 +180,11 @@ pub const text_private = [text_private_len]u8;
177pub const text_public = [text_public_len]u8; 180pub const text_public = [text_public_len]u8;
178pub const text_seed = [text_seed_len]u8; 181pub const text_seed = [text_seed_len]u8;
179 182
180pub fn encodePublic(prefix: PublicPrefixByte, key: *const [Ed25519.public_length]u8) !text_public { 183fn encodePublic(prefix: PublicPrefixByte, key: *const [Ed25519.public_length]u8) text_public {
181 return encode(1, key.len, &[_]u8{@enumToInt(prefix)}, key); 184 return encode(1, key.len, &[_]u8{@enumToInt(prefix)}, key);
182} 185}
183 186
184pub fn encodePrivate(key: *const [Ed25519.secret_length]u8) !text_private { 187fn encodePrivate(key: *const [Ed25519.secret_length]u8) text_private {
185 return encode(1, key.len, &[_]u8{@enumToInt(KeyTypePrefixByte.private)}, key); 188 return encode(1, key.len, &[_]u8{@enumToInt(KeyTypePrefixByte.private)}, key);
186} 189}
187 190
@@ -194,7 +197,7 @@ fn encode(
194 comptime data_len: usize, 197 comptime data_len: usize,
195 prefix: *const [prefix_len]u8, 198 prefix: *const [prefix_len]u8,
196 data: *const [data_len]u8, 199 data: *const [data_len]u8,
197) !encoded_key(prefix_len, data_len) { 200) encoded_key(prefix_len, data_len) {
198 var buf: [prefix_len + data_len + 2]u8 = undefined; 201 var buf: [prefix_len + data_len + 2]u8 = undefined;
199 defer wipeBytes(&buf); 202 defer wipeBytes(&buf);
200 203
@@ -210,7 +213,7 @@ fn encode(
210 return text; 213 return text;
211} 214}
212 215
213pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) !text_seed { 216pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) text_seed {
214 const full_prefix = &[_]u8{ 217 const full_prefix = &[_]u8{
215 @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(prefix) >> 5), 218 @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(prefix) >> 5),
216 (@enumToInt(prefix) & 0b00011111) << 3, 219 (@enumToInt(prefix) & 0b00011111) << 3,
@@ -218,21 +221,7 @@ pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8)
218 return encode(full_prefix.len, src.len, full_prefix, src); 221 return encode(full_prefix.len, src.len, full_prefix, src);
219} 222}
220 223
221pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 { 224pub const DecodeError = InvalidPrefixByteError || base32.DecodeError || crc16.InvalidChecksumError;
222 var decoded = try decode(1, Ed25519.secret_length, text);
223 defer decoded.wipe();
224 if (decoded.prefix[0] != @enumToInt(KeyTypePrefixByte.private))
225 return error.InvalidPrefixByte;
226 return decoded.data;
227}
228
229pub fn decodePublic(prefix: PublicPrefixByte, text: *const text_public) ![Ed25519.public_length]u8 {
230 var decoded = try decode(1, Ed25519.public_length, text);
231 defer decoded.wipe();
232 if (decoded.data[0] != @enumToInt(prefix))
233 return error.InvalidPrefixByte;
234 return decoded.data;
235}
236 225
237fn DecodedNkey(comptime prefix_len: usize, comptime data_len: usize) type { 226fn DecodedNkey(comptime prefix_len: usize, comptime data_len: usize) type {
238 return struct { 227 return struct {
@@ -252,7 +241,7 @@ fn decode(
252 comptime prefix_len: usize, 241 comptime prefix_len: usize,
253 comptime data_len: usize, 242 comptime data_len: usize,
254 text: *const [base32.Encoder.calcSize(prefix_len + data_len + 2)]u8, 243 text: *const [base32.Encoder.calcSize(prefix_len + data_len + 2)]u8,
255) !DecodedNkey(prefix_len, data_len) { 244) (base32.DecodeError || crc16.InvalidChecksumError)!DecodedNkey(prefix_len, data_len) {
256 var raw: [prefix_len + data_len + 2]u8 = undefined; 245 var raw: [prefix_len + data_len + 2]u8 = undefined;
257 defer wipeBytes(&raw); 246 defer wipeBytes(&raw);
258 std.debug.assert((try base32.Decoder.decode(&raw, text[0..])).len == raw.len); 247 std.debug.assert((try base32.Decoder.decode(&raw, text[0..])).len == raw.len);
@@ -278,7 +267,9 @@ pub const DecodedSeed = struct {
278 } 267 }
279}; 268};
280 269
281pub fn decodeSeed(text: *const text_seed) !DecodedSeed { 270pub const SeedDecodeError = DecodeError || InvalidSeedError;
271
272pub fn decodeSeed(text: *const text_seed) SeedDecodeError!DecodedSeed {
282 var decoded = try decode(2, Ed25519.seed_length, text); 273 var decoded = try decode(2, Ed25519.seed_length, text);
283 defer decoded.wipe(); // gets copied 274 defer decoded.wipe(); // gets copied
284 275
@@ -328,65 +319,50 @@ pub fn isValidPublicKey(text: *const text_public, with_type: ?PublicPrefixByte)
328 return if (with_type) |ty| public == ty else true; 319 return if (with_type) |ty| public == ty else true;
329} 320}
330 321
331pub fn getNextLine(text: []const u8, off: *usize) ?[]const u8 {
332 if (off.* >= text.len) return null;
333 const newline_pos = mem.indexOfPos(u8, text, off.*, "\n") orelse return null;
334 const start = off.*;
335 var end = newline_pos;
336 if (newline_pos > 0 and text[newline_pos - 1] == '\r') end -= 1;
337 off.* = newline_pos + 1;
338 return text[start..end];
339}
340
341// `line` must not contain CR or LF characters. 322// `line` must not contain CR or LF characters.
342pub fn isKeySectionBarrier(line: []const u8) bool { 323pub fn isKeySectionBarrier(line: []const u8) bool {
343 return line.len >= 6 and mem.startsWith(u8, line, "---") and mem.endsWith(u8, line, "---"); 324 return line.len >= 6 and mem.startsWith(u8, line, "---") and mem.endsWith(u8, line, "---");
344} 325}
345 326
346pub fn areKeySectionContentsValid(contents: []const u8) bool { 327const allowed_creds_section_chars_table: [256]bool = allowed: {
347 const allowed_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.="; 328 @setEvalBranchQuota(256);
348 329
349 for (contents) |c| { 330 var table = [_]bool{false} ** 256;
350 var is_c_allowed = false; 331 const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.=";
351 for (allowed_chars) |allowed_c| { 332 for (chars) |char| table[char] = true;
352 if (c == allowed_c) {
353 is_c_allowed = true;
354 break;
355 }
356 }
357 if (!is_c_allowed) return false;
358 }
359 333
334 break :allowed table;
335};
336
337pub fn areKeySectionContentsValid(contents: []const u8) bool {
338 for (contents) |c| if (!allowed_creds_section_chars_table[c]) return false;
360 return true; 339 return true;
361} 340}
362 341
363pub fn findKeySection(text: []const u8, off: *usize) ?[]const u8 { 342pub fn findKeySection(text: []const u8, line_it: *std.mem.SplitIterator) ?[]const u8 {
364 // Skip all space 343 // TODO(rutgerbrf): There is a weird edge case in the github.com/nats-io/nkeys library,
365 // Lines end with \n, but \r\n is also fine 344 // see https://regex101.com/r/pEaqcJ/1. It allows the opening barrier to start at an
366 // Contents of the key may consist of abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.= 345 // arbitrary point on the line, meaning that `asdf-----BEGIN USER NKEY SEED-----`
367 // However, if a line seems to be in the form of ---stuff---, the section is ended. 346 // is regarded as a valid opening barrier by the library.
368 // A newline must be present at the end of the key footer 347 // Should we accept a creds file formatted in such a manner?
369 // See https://regex101.com/r/pEaqcJ/1 for a weird edge case in the github.com/nats-io/nkeys library
370 // Another weird edge case: https://regex101.com/r/Xmqj1h/1
371 348
372 // TODO(rutgerbrf): switch to std.mem.SplitIterator
373 while (true) { 349 while (true) {
374 const opening_line = getNextLine(text, off) orelse return null; 350 const opening_line = line_it.next() orelse return null;
375 if (!isKeySectionBarrier(opening_line)) continue; 351 if (!isKeySectionBarrier(opening_line)) continue;
376 352
377 const contents_line = getNextLine(text, off) orelse return null; 353 const contents_line = line_it.next() orelse return null;
378 if (!areKeySectionContentsValid(contents_line)) continue; 354 if (!areKeySectionContentsValid(contents_line)) continue;
379 355
380 const closing_line = getNextLine(text, off) orelse return null; 356 const closing_line = line_it.next() orelse return null;
381 if (!isKeySectionBarrier(closing_line)) continue; 357 if (!isKeySectionBarrier(closing_line)) continue;
382 358
383 return contents_line; 359 return contents_line;
384 } 360 }
385} 361}
386 362
387pub fn parseDecoratedJwt(contents: []const u8) ![]const u8 { 363pub fn parseDecoratedJwt(contents: []const u8) []const u8 {
388 var current_off: usize = 0; 364 var line_it = mem.split(contents, "\n");
389 return findKeySection(contents, &current_off) orelse return contents; 365 return findKeySection(contents, &line_it) orelse return contents;
390} 366}
391 367
392fn validNkey(text: []const u8) bool { 368fn validNkey(text: []const u8) bool {
@@ -399,9 +375,9 @@ fn validNkey(text: []const u8) bool {
399} 375}
400 376
401fn findNkey(text: []const u8) ?[]const u8 { 377fn findNkey(text: []const u8) ?[]const u8 {
378 var line_it = std.mem.split(text, "\n");
402 var current_off: usize = 0; 379 var current_off: usize = 0;
403 while (true) { 380 while (line_it.next()) |line| {
404 var line = getNextLine(text, &current_off) orelse return null;
405 for (line) |c, i| { 381 for (line) |c, i| {
406 if (!ascii.isSpace(c)) { 382 if (!ascii.isSpace(c)) {
407 if (validNkey(line[i..])) return line[i..]; 383 if (validNkey(line[i..])) return line[i..];
@@ -409,21 +385,23 @@ fn findNkey(text: []const u8) ?[]const u8 {
409 } 385 }
410 } 386 }
411 } 387 }
388 return null;
412} 389}
413 390
414pub fn parseDecoratedNkey(contents: []const u8) !SeedKeyPair { 391pub fn parseDecoratedNkey(contents: []const u8) NoNkeySeedFoundError!SeedKeyPair {
392 var line_it = mem.split(contents, "\n");
415 var current_off: usize = 0; 393 var current_off: usize = 0;
416 var seed: ?[]const u8 = null; 394 var seed: ?[]const u8 = null;
417 if (findKeySection(contents, &current_off) != null) 395 if (findKeySection(contents, &line_it) != null)
418 seed = findKeySection(contents, &current_off); 396 seed = findKeySection(contents, &line_it);
419 if (seed == null) 397 if (seed == null)
420 seed = findNkey(contents) orelse return error.NoNkeySeedFound; 398 seed = findNkey(contents) orelse return error.NoNkeySeedFound;
421 if (!validNkey(seed.?)) 399 if (!validNkey(seed.?))
422 return error.NoNkeySeedFound; 400 return error.NoNkeySeedFound;
423 return SeedKeyPair.fromTextSeed(seed.?[0..text_seed_len]); 401 return SeedKeyPair.fromTextSeed(seed.?[0..text_seed_len]) catch return error.NoNkeySeedFound;
424} 402}
425 403
426pub fn parseDecoratedUserNkey(contents: []const u8) !SeedKeyPair { 404pub fn parseDecoratedUserNkey(contents: []const u8) (NoNkeySeedFoundError || NoNkeyUserSeedFoundError)!SeedKeyPair {
427 var key = try parseDecoratedNkey(contents); 405 var key = try parseDecoratedNkey(contents);
428 if (!mem.startsWith(u8, &key.seed, "SU")) return error.NoNkeyUserSeedFound; 406 if (!mem.startsWith(u8, &key.seed, "SU")) return error.NoNkeyUserSeedFound;
429 defer key.wipe(); 407 defer key.wipe();
@@ -437,12 +415,12 @@ test {
437} 415}
438 416
439test { 417test {
440 var key_pair = try SeedKeyPair.generate(PublicPrefixByte.server); 418 var key_pair = SeedKeyPair.generate(PublicPrefixByte.server);
441 defer key_pair.wipe(); 419 defer key_pair.wipe();
442 420
443 var decoded_seed = try decodeSeed(&key_pair.seed); 421 var decoded_seed = try decodeSeed(&key_pair.seed);
444 defer decoded_seed.wipe(); 422 defer decoded_seed.wipe();
445 var encoded_second_time = try encodeSeed(decoded_seed.prefix, &decoded_seed.seed); 423 var encoded_second_time = encodeSeed(decoded_seed.prefix, &decoded_seed.seed);
446 defer wipeBytes(&encoded_second_time); 424 defer wipeBytes(&encoded_second_time);
447 try testing.expectEqualSlices(u8, &key_pair.seed, &encoded_second_time); 425 try testing.expectEqualSlices(u8, &key_pair.seed, &encoded_second_time);
448 try testing.expect(isValidEncoding(&key_pair.seed)); 426 try testing.expect(isValidEncoding(&key_pair.seed));
@@ -458,7 +436,7 @@ test {
458 436
459 var pub_key = try key_pair.intoPublicKey(); 437 var pub_key = try key_pair.intoPublicKey();
460 defer pub_key.wipe(); 438 defer pub_key.wipe();
461 var pub_key_str_b = try pub_key.publicKey(); 439 var pub_key_str_b = pub_key.publicKey();
462 defer wipeBytes(&pub_key_str_b); 440 defer wipeBytes(&pub_key_str_b);
463 try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b); 441 try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b);
464} 442}
@@ -470,5 +448,5 @@ test {
470 448
471 // TODO(rutgerbrf): validate the contents of the results of these functions 449 // TODO(rutgerbrf): validate the contents of the results of these functions
472 _ = try parseDecoratedUserNkey(creds_bytes); 450 _ = try parseDecoratedUserNkey(creds_bytes);
473 _ = try parseDecoratedJwt(creds_bytes); 451 _ = parseDecoratedJwt(creds_bytes);
474} 452}
diff --git a/src/znk.zig b/src/znk.zig
index 1c2898b..5a5ab5e 100644
--- a/src/znk.zig
+++ b/src/znk.zig
@@ -147,7 +147,7 @@ pub fn cmdGen(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !voi
147 147
148 try PrefixKeyGenerator.init(arena, ty.?, capitalized_prefix).generate(); 148 try PrefixKeyGenerator.init(arena, ty.?, capitalized_prefix).generate();
149 } else { 149 } else {
150 var kp = nkeys.SeedKeyPair.generate(ty.?) catch |e| fatal("could not generate key pair: {e}", .{e}); 150 var kp = nkeys.SeedKeyPair.generate(ty.?);
151 defer kp.wipe(); 151 defer kp.wipe();
152 try stdout.writeAll(&kp.seed); 152 try stdout.writeAll(&kp.seed);
153 try stdout.writeAll("\n"); 153 try stdout.writeAll("\n");
@@ -231,7 +231,7 @@ pub fn cmdSign(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !vo
231 const content = file.?.readToEndAlloc(arena, std.math.maxInt(usize)) catch { 231 const content = file.?.readToEndAlloc(arena, std.math.maxInt(usize)) catch {
232 fatal("could not read file to generate signature for", .{}); 232 fatal("could not read file to generate signature for", .{});
233 }; 233 };
234 var kp = switch (readKeyFile(arena, key.?)) { 234 var kp = switch (readKeyFile(arena, key.?) orelse fatal("could not find a valid key", .{})) {
235 .seed_key_pair => |kp| kp, 235 .seed_key_pair => |kp| kp,
236 else => |*k| { 236 else => |*k| {
237 k.wipe(); 237 k.wipe();
@@ -339,7 +339,7 @@ pub fn cmdVerify(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !
339 const signature_b64 = sig.?.readToEndAlloc(arena, std.math.maxInt(usize)) catch { 339 const signature_b64 = sig.?.readToEndAlloc(arena, std.math.maxInt(usize)) catch {
340 fatal("could not read signature", .{}); 340 fatal("could not read signature", .{});
341 }; 341 };
342 var k = readKeyFile(arena, key.?); 342 var k = readKeyFile(arena, key.?) orelse fatal("could not find a valid key", .{});
343 defer k.wipe(); 343 defer k.wipe();
344 344
345 const trimmed_signature_b64 = mem.trim(u8, signature_b64, " \n\t\r"); 345 const trimmed_signature_b64 = mem.trim(u8, signature_b64, " \n\t\r");
@@ -381,7 +381,7 @@ const PrefixKeyGenerator = struct {
381 while (true) { 381 while (true) {
382 if (self.done.load(.SeqCst)) return; 382 if (self.done.load(.SeqCst)) return;
383 383
384 var kp = nkeys.SeedKeyPair.generate(self.ty) catch |e| fatal("could not generate key pair: {e}", .{e}); 384 var kp = nkeys.SeedKeyPair.generate(self.ty);
385 defer kp.wipe(); 385 defer kp.wipe();
386 var public_key = kp.publicKey() catch |e| fatal("could not generate public key: {e}", .{e}); 386 var public_key = kp.publicKey() catch |e| fatal("could not generate public key: {e}", .{e});
387 if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue; 387 if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue;
@@ -435,7 +435,7 @@ pub const Nkey = union(enum) {
435 pub fn publicKey(self: *const Self) !nkeys.text_public { 435 pub fn publicKey(self: *const Self) !nkeys.text_public {
436 return switch (self.*) { 436 return switch (self.*) {
437 .seed_key_pair => |*kp| try kp.publicKey(), 437 .seed_key_pair => |*kp| try kp.publicKey(),
438 .public_key => |*pk| try pk.publicKey(), 438 .public_key => |*pk| pk.publicKey(),
439 }; 439 };
440 } 440 }
441 441
@@ -481,20 +481,23 @@ pub const Nkey = union(enum) {
481 } 481 }
482}; 482};
483 483
484pub fn readKeyFile(allocator: *Allocator, file: fs.File) Nkey { 484pub fn readKeyFile(allocator: *Allocator, file: fs.File) ?Nkey {
485 var bytes = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch fatal("could not read key file", .{}); 485 var bytes = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch fatal("could not read key file", .{});
486 defer {
487 for (bytes) |*b| b.* = 0;
488 allocator.free(bytes);
489 }
486 490
487 var iterator = mem.split(bytes, "\n"); 491 var iterator = mem.split(bytes, "\n");
488 while (iterator.next()) |line| { 492 while (iterator.next()) |line| {
489 if (nkeys.isValidEncoding(line) and line.len == nkeys.text_seed_len) { 493 if (nkeys.isValidEncoding(line) and line.len == nkeys.text_seed_len) {
490 var k = Nkey.fromText(line) catch continue; 494 var k = Nkey.fromText(line) catch continue;
491 defer k.wipe(); 495 defer k.wipe();
492 allocator.free(bytes);
493 return k; 496 return k;
494 } 497 }
495 } 498 }
496 499
497 fatal("could not find a valid key", .{}); 500 return null;
498} 501}
499 502
500test { 503test {