aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Rutger Broekhoff2021-05-22 21:19:53 +0200
committerLibravatar Rutger Broekhoff2021-05-22 21:19:53 +0200
commit07ac8dc00e7676d7845a881970c259679b0c5d23 (patch)
treeb2ea799f35eab953d083472ea435ca101e7f321f
parent4716921ce42b5153b5c60edf3638e1592f72f743 (diff)
downloadzig-nkeys-07ac8dc00e7676d7845a881970c259679b0c5d23.tar.gz
zig-nkeys-07ac8dc00e7676d7845a881970c259679b0c5d23.zip
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.
-rw-r--r--src/base32.zig14
-rw-r--r--src/nkeys.zig189
-rw-r--r--src/znk.zig93
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 {
182 182
183 /// Get a character from the buffer. 183 /// Get a character from the buffer.
184 fn decodeChar(c: u8) DecodeError!u5 { 184 fn decodeChar(c: u8) DecodeError!u5 {
185 var value: u5 = 0;
186 if (c >= 'A' and c <= 'Z') { 185 if (c >= 'A' and c <= 'Z') {
187 value = @truncate(u5, c - @as(u8, 'A')); 186 return @truncate(u5, c - @as(u8, 'A'));
188 } else if (c >= '2' and c <= '9') { 187 } else if (c >= '2' and c <= '9') {
189 // '2' -> 26 188 // '2' -> 26
190 value = @truncate(u5, c - @as(u8, '2') + 26); 189 return @truncate(u5, c - @as(u8, '2') + 26);
191 } else { 190 } else {
192 return error.CorruptInputError; 191 return error.CorruptInputError;
193 } 192 }
194 return value;
195 } 193 }
196 194
197 /// Get the next 5-bit decoded character, read from `self.buffer`. 195 /// Get the next 5-bit decoded character, read from `self.buffer`.
@@ -248,12 +246,12 @@ test {
248 const decoded = "this is a test"; 246 const decoded = "this is a test";
249 247
250 var decode_buf: [Decoder.calcSize(encoded.len)]u8 = undefined; 248 var decode_buf: [Decoder.calcSize(encoded.len)]u8 = undefined;
251 var decode_res = try Decoder.decode(&decode_buf, encoded); 249 const decode_res = try Decoder.decode(&decode_buf, encoded);
252 250
253 try testing.expectEqualStrings(decoded, decode_res); 251 try testing.expectEqualStrings(decoded, decode_res);
254 252
255 var encode_buf: [Encoder.calcSize(decoded.len)]u8 = undefined; 253 var encode_buf: [Encoder.calcSize(decoded.len)]u8 = undefined;
256 var encode_res = Encoder.encode(&encode_buf, decoded); 254 const encode_res = Encoder.encode(&encode_buf, decoded);
257 255
258 try testing.expectEqualStrings(encoded, encode_res); 256 try testing.expectEqualStrings(encoded, encode_res);
259} 257}
@@ -263,12 +261,12 @@ test {
263 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 }; 261 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 };
264 262
265 var decode_buf: [Decoder.calcSize(encoded.len)]u8 = undefined; 263 var decode_buf: [Decoder.calcSize(encoded.len)]u8 = undefined;
266 var decode_res = try Decoder.decode(&decode_buf, encoded); 264 const decode_res = try Decoder.decode(&decode_buf, encoded);
267 265
268 try testing.expectEqualSlices(u8, decoded, decode_res); 266 try testing.expectEqualSlices(u8, decoded, decode_res);
269 267
270 var encode_buf: [Encoder.calcSize(decoded.len)]u8 = undefined; 268 var encode_buf: [Encoder.calcSize(decoded.len)]u8 = undefined;
271 var encode_res = Encoder.encode(&encode_buf, decoded); 269 const encode_res = Encoder.encode(&encode_buf, decoded);
272 270
273 try testing.expectEqualSlices(u8, encoded, encode_res); 271 try testing.expectEqualSlices(u8, encoded, encode_res);
274} 272}
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{
11 InvalidPrefixByte, 11 InvalidPrefixByte,
12 InvalidEncoding, 12 InvalidEncoding,
13 InvalidSeed, 13 InvalidSeed,
14 NoNKeySeedFound, 14 NoNkeySeedFound,
15 NoNKeyUserSeedFound, 15 NoNkeyUserSeedFound,
16};
17
18pub fn fromText(text: []const u8) !Key {
19 if (!isValidEncoding(text)) return error.InvalidEncoding;
20 switch (text[0]) {
21 'S' => {
22 // It's a seed.
23 if (text.len != text_seed_len) return error.InvalidSeed;
24 return Key{ .seed_key_pair = try fromSeed(text[0..text_seed_len]) };
25 },
26 'P' => return error.InvalidEncoding, // unsupported for now
27 else => {
28 if (text.len != text_public_len) return error.InvalidEncoding;
29 return Key{ .public_key = try fromPublicKey(text[0..text_public_len]) };
30 },
31 }
32}
33
34pub const Key = union(enum) {
35 seed_key_pair: SeedKeyPair,
36 public_key: PublicKey,
37
38 const Self = @This();
39
40 pub fn publicKey(self: *const Self) !text_public {
41 return switch (self.*) {
42 .seed_key_pair => |*kp| try kp.publicKey(),
43 .public_key => |*pk| try pk.publicKey(),
44 };
45 }
46
47 pub fn intoPublicKey(self: *const Self) !PublicKey {
48 return switch (self.*) {
49 .seed_key_pair => |*kp| try kp.intoPublicKey(),
50 .public_key => |pk| pk,
51 };
52 }
53
54 pub fn verify(
55 self: *const Self,
56 msg: []const u8,
57 sig: [Ed25519.signature_length]u8,
58 ) !void {
59 return switch (self.*) {
60 .seed_key_pair => |*kp| try kp.verify(msg, sig),
61 .public_key => |*pk| try pk.verify(msg, sig),
62 };
63 }
64
65 pub fn wipe(self: *Self) void {
66 return switch (self.*) {
67 .seed_key_pair => |*kp| kp.wipe(),
68 .public_key => |*pk| pk.wipe(),
69 };
70 }
71}; 16};
72 17
73pub const KeyTypePrefixByte = enum(u8) { 18pub const KeyTypePrefixByte = enum(u8) {
@@ -100,22 +45,24 @@ pub const SeedKeyPair = struct {
100 45
101 seed: text_seed, 46 seed: text_seed,
102 47
103 pub fn init(prefix: PublicPrefixByte) !Self { 48 pub fn generate(prefix: PublicPrefixByte) !Self {
104 var raw_seed: [Ed25519.seed_length]u8 = undefined; 49 var raw_seed: [Ed25519.seed_length]u8 = undefined;
105 crypto.random.bytes(&raw_seed); 50 crypto.random.bytes(&raw_seed);
106 defer wipeBytes(&raw_seed); 51 defer wipeBytes(&raw_seed);
107 52
108 var seed = try encodeSeed(prefix, &raw_seed); 53 return Self{ .seed = try encodeSeed(prefix, &raw_seed) };
109 return Self{ .seed = seed };
110 } 54 }
111 55
112 pub fn initFromSeed(seed: *const text_seed) !Self { 56 pub fn fromTextSeed(seed: *const text_seed) !Self {
113 var decoded = try decodeSeed(seed); 57 var decoded = try decodeSeed(seed);
114 defer decoded.wipe(); 58 decoded.wipe();
115
116 return Self{ .seed = seed.* }; 59 return Self{ .seed = seed.* };
117 } 60 }
118 61
62 pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) !Self {
63 return Self{ .seed = try encodeSeed(prefix, raw_seed) };
64 }
65
119 fn rawSeed(self: *const Self) ![Ed25519.seed_length]u8 { 66 fn rawSeed(self: *const Self) ![Ed25519.seed_length]u8 {
120 return (try decodeSeed(&self.seed)).seed; 67 return (try decodeSeed(&self.seed)).seed;
121 } 68 }
@@ -140,6 +87,7 @@ pub const SeedKeyPair = struct {
140 87
141 pub fn intoPublicKey(self: *const Self) !PublicKey { 88 pub fn intoPublicKey(self: *const Self) !PublicKey {
142 var decoded = try decodeSeed(&self.seed); 89 var decoded = try decodeSeed(&self.seed);
90 defer decoded.wipe();
143 var kp = try Ed25519.KeyPair.create(decoded.seed); 91 var kp = try Ed25519.KeyPair.create(decoded.seed);
144 defer wipeKeyPair(&kp); 92 defer wipeKeyPair(&kp);
145 return PublicKey{ 93 return PublicKey{
@@ -186,6 +134,16 @@ pub const PublicKey = struct {
186 prefix: PublicPrefixByte, 134 prefix: PublicPrefixByte,
187 key: [Ed25519.public_length]u8, 135 key: [Ed25519.public_length]u8,
188 136
137 pub fn fromTextPublicKey(text: *const text_public) !PublicKey {
138 var decoded = try decode(1, Ed25519.public_length, text);
139 defer decoded.wipe(); // gets copied
140
141 return PublicKey{
142 .prefix = try PublicPrefixByte.fromU8(decoded.prefix[0]),
143 .key = decoded.data,
144 };
145 }
146
189 pub fn publicKey(self: *const Self) !text_public { 147 pub fn publicKey(self: *const Self) !text_public {
190 return try encodePublic(self.prefix, &self.key); 148 return try encodePublic(self.prefix, &self.key);
191 } 149 }
@@ -199,8 +157,8 @@ pub const PublicKey = struct {
199 } 157 }
200 158
201 pub fn wipe(self: *Self) void { 159 pub fn wipe(self: *Self) void {
202 self.prefix = .user; 160 self.prefix = .account;
203 std.crypto.random.bytes(&self.key); 161 wipeBytes(&self.key);
204 } 162 }
205}; 163};
206 164
@@ -247,22 +205,22 @@ fn encode(
247 mem.writeIntLittle(u16, buf[buf.len - 2 .. buf.len], checksum); 205 mem.writeIntLittle(u16, buf[buf.len - 2 .. buf.len], checksum);
248 206
249 var text: encoded_key(prefix_len, data_len) = undefined; 207 var text: encoded_key(prefix_len, data_len) = undefined;
250 _ = base32.Encoder.encode(&text, &buf); 208 std.debug.assert(base32.Encoder.encode(&text, &buf).len == text.len);
251 209
252 return text; 210 return text;
253} 211}
254 212
255pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) !text_seed { 213pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) !text_seed {
256 var full_prefix = [_]u8{ 214 const full_prefix = &[_]u8{
257 @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(prefix) >> 5), 215 @enumToInt(KeyTypePrefixByte.seed) | (@enumToInt(prefix) >> 5),
258 (@enumToInt(prefix) & 0b00011111) << 3, 216 (@enumToInt(prefix) & 0b00011111) << 3,
259 }; 217 };
260 return encode(full_prefix.len, src.len, &full_prefix, src); 218 return encode(full_prefix.len, src.len, full_prefix, src);
261} 219}
262 220
263pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 { 221pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 {
264 var decoded = try decode(1, Ed25519.secret_length, text); 222 var decoded = try decode(1, Ed25519.secret_length, text);
265 defer wipeBytes(&decoded.data); 223 defer decoded.wipe();
266 if (decoded.prefix[0] != @enumToInt(KeyTypePrefixByte.private)) 224 if (decoded.prefix[0] != @enumToInt(KeyTypePrefixByte.private))
267 return error.InvalidPrefixByte; 225 return error.InvalidPrefixByte;
268 return decoded.data; 226 return decoded.data;
@@ -270,15 +228,23 @@ pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 {
270 228
271pub fn decodePublic(prefix: PublicPrefixByte, text: *const text_public) ![Ed25519.public_length]u8 { 229pub fn decodePublic(prefix: PublicPrefixByte, text: *const text_public) ![Ed25519.public_length]u8 {
272 var decoded = try decode(1, Ed25519.public_length, text); 230 var decoded = try decode(1, Ed25519.public_length, text);
231 defer decoded.wipe();
273 if (decoded.data[0] != @enumToInt(prefix)) 232 if (decoded.data[0] != @enumToInt(prefix))
274 return error.InvalidPrefixByte; 233 return error.InvalidPrefixByte;
275 return decoded.data; 234 return decoded.data;
276} 235}
277 236
278fn DecodedNKey(comptime prefix_len: usize, comptime data_len: usize) type { 237fn DecodedNkey(comptime prefix_len: usize, comptime data_len: usize) type {
279 return struct { 238 return struct {
239 const Self = @This();
240
280 prefix: [prefix_len]u8, 241 prefix: [prefix_len]u8,
281 data: [data_len]u8, 242 data: [data_len]u8,
243
244 pub fn wipe(self: *Self) void {
245 self.prefix[0] = @enumToInt(PublicPrefixByte.account);
246 wipeBytes(&self.data);
247 }
282 }; 248 };
283} 249}
284 250
@@ -286,15 +252,15 @@ fn decode(
286 comptime prefix_len: usize, 252 comptime prefix_len: usize,
287 comptime data_len: usize, 253 comptime data_len: usize,
288 text: *const [base32.Encoder.calcSize(prefix_len + data_len + 2)]u8, 254 text: *const [base32.Encoder.calcSize(prefix_len + data_len + 2)]u8,
289) !DecodedNKey(prefix_len, data_len) { 255) !DecodedNkey(prefix_len, data_len) {
290 var raw: [prefix_len + data_len + 2]u8 = undefined; 256 var raw: [prefix_len + data_len + 2]u8 = undefined;
291 defer wipeBytes(&raw); 257 defer wipeBytes(&raw);
292 _ = try base32.Decoder.decode(&raw, text[0..]); 258 std.debug.assert((try base32.Decoder.decode(&raw, text[0..])).len == raw.len);
293 259
294 var checksum = mem.readIntLittle(u16, raw[raw.len - 2 .. raw.len]); 260 var checksum = mem.readIntLittle(u16, raw[raw.len - 2 .. raw.len]);
295 try crc16.validate(raw[0 .. raw.len - 2], checksum); 261 try crc16.validate(raw[0 .. raw.len - 2], checksum);
296 262
297 return DecodedNKey(prefix_len, data_len){ 263 return DecodedNkey(prefix_len, data_len){
298 .prefix = raw[0..prefix_len].*, 264 .prefix = raw[0..prefix_len].*,
299 .data = raw[prefix_len .. raw.len - 2].*, 265 .data = raw[prefix_len .. raw.len - 2].*,
300 }; 266 };
@@ -314,7 +280,7 @@ pub const DecodedSeed = struct {
314 280
315pub fn decodeSeed(text: *const text_seed) !DecodedSeed { 281pub fn decodeSeed(text: *const text_seed) !DecodedSeed {
316 var decoded = try decode(2, Ed25519.seed_length, text); 282 var decoded = try decode(2, Ed25519.seed_length, text);
317 defer wipeBytes(&decoded.data); // gets copied 283 defer decoded.wipe(); // gets copied
318 284
319 var key_ty_prefix = decoded.prefix[0] & 0b11111000; 285 var key_ty_prefix = decoded.prefix[0] & 0b11111000;
320 var entity_ty_prefix = (decoded.prefix[0] & 0b00000111) << 5 | ((decoded.prefix[1] & 0b11111000) >> 3); 286 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 {
328 }; 294 };
329} 295}
330 296
331pub fn fromPublicKey(text: *const text_public) !PublicKey {
332 var decoded = try decode(1, Ed25519.public_length, text);
333 defer wipeBytes(&decoded.data); // gets copied
334
335 return PublicKey{
336 .prefix = try PublicPrefixByte.fromU8(decoded.prefix[0]),
337 .key = decoded.data,
338 };
339}
340
341pub fn fromSeed(text: *const text_seed) !SeedKeyPair {
342 var res = try decodeSeed(text);
343 wipeBytes(&res.seed);
344 return SeedKeyPair{ .seed = text.* };
345}
346
347pub fn isValidEncoding(text: []const u8) bool { 297pub fn isValidEncoding(text: []const u8) bool {
348 if (text.len < 4) return false; 298 if (text.len < 4) return false;
349 var made_crc: u16 = 0; 299 var made_crc: u16 = 0;
@@ -367,24 +317,21 @@ pub fn isValidEncoding(text: []const u8) bool {
367 317
368pub fn isValidSeed(text: *const text_seed) bool { 318pub fn isValidSeed(text: *const text_seed) bool {
369 var res = decodeSeed(text) catch return false; 319 var res = decodeSeed(text) catch return false;
370 wipeBytes(&res.seed); 320 res.wipe();
371 return true; 321 return true;
372} 322}
373 323
374pub fn isValidPublicKey(text: *const text_public, with_type: ?PublicPrefixByte) bool { 324pub fn isValidPublicKey(text: *const text_public, with_type: ?PublicPrefixByte) bool {
375 var res = decode(1, Ed25519.public_length, text) catch return false; 325 var res = decode(1, Ed25519.public_length, text) catch return false;
376 var public = PublicPrefixByte.fromU8(res.data[0]) catch return false; 326 defer res.wipe();
327 const public = PublicPrefixByte.fromU8(res.data[0]) catch return false;
377 return if (with_type) |ty| public == ty else true; 328 return if (with_type) |ty| public == ty else true;
378} 329}
379 330
380pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) !SeedKeyPair {
381 return SeedKeyPair{ .seed = try encodeSeed(prefix, raw_seed) };
382}
383
384pub fn getNextLine(text: []const u8, off: *usize) ?[]const u8 { 331pub fn getNextLine(text: []const u8, off: *usize) ?[]const u8 {
385 if (off.* >= text.len) return null; 332 if (off.* >= text.len) return null;
386 var newline_pos = mem.indexOfPos(u8, text, off.*, "\n") orelse return null; 333 const newline_pos = mem.indexOfPos(u8, text, off.*, "\n") orelse return null;
387 var start = off.*; 334 const start = off.*;
388 var end = newline_pos; 335 var end = newline_pos;
389 if (newline_pos > 0 and text[newline_pos - 1] == '\r') end -= 1; 336 if (newline_pos > 0 and text[newline_pos - 1] == '\r') end -= 1;
390 off.* = newline_pos + 1; 337 off.* = newline_pos + 1;
@@ -424,13 +371,13 @@ pub fn findKeySection(text: []const u8, off: *usize) ?[]const u8 {
424 371
425 // TODO(rutgerbrf): switch to std.mem.SplitIterator 372 // TODO(rutgerbrf): switch to std.mem.SplitIterator
426 while (true) { 373 while (true) {
427 var opening_line = getNextLine(text, off) orelse return null; 374 const opening_line = getNextLine(text, off) orelse return null;
428 if (!isKeySectionBarrier(opening_line)) continue; 375 if (!isKeySectionBarrier(opening_line)) continue;
429 376
430 var contents_line = getNextLine(text, off) orelse return null; 377 const contents_line = getNextLine(text, off) orelse return null;
431 if (!areKeySectionContentsValid(contents_line)) continue; 378 if (!areKeySectionContentsValid(contents_line)) continue;
432 379
433 var closing_line = getNextLine(text, off) orelse return null; 380 const closing_line = getNextLine(text, off) orelse return null;
434 if (!isKeySectionBarrier(closing_line)) continue; 381 if (!isKeySectionBarrier(closing_line)) continue;
435 382
436 return contents_line; 383 return contents_line;
@@ -442,82 +389,86 @@ pub fn parseDecoratedJwt(contents: []const u8) ![]const u8 {
442 return findKeySection(contents, &current_off) orelse return contents; 389 return findKeySection(contents, &current_off) orelse return contents;
443} 390}
444 391
445fn validNKey(text: []const u8) bool { 392fn validNkey(text: []const u8) bool {
446 var valid_prefix = 393 const valid_prefix =
447 mem.startsWith(u8, text, "SO") or 394 mem.startsWith(u8, text, "SO") or
448 mem.startsWith(u8, text, "SA") or 395 mem.startsWith(u8, text, "SA") or
449 mem.startsWith(u8, text, "SU"); 396 mem.startsWith(u8, text, "SU");
450 var valid_len = text.len >= text_seed_len; 397 const valid_len = text.len >= text_seed_len;
451 return valid_prefix and valid_len; 398 return valid_prefix and valid_len;
452} 399}
453 400
454fn findNKey(text: []const u8) ?[]const u8 { 401fn findNkey(text: []const u8) ?[]const u8 {
455 var current_off: usize = 0; 402 var current_off: usize = 0;
456 while (true) { 403 while (true) {
457 var line = getNextLine(text, &current_off) orelse return null; 404 var line = getNextLine(text, &current_off) orelse return null;
458 for (line) |c, i| { 405 for (line) |c, i| {
459 if (!ascii.isSpace(c)) { 406 if (!ascii.isSpace(c)) {
460 if (validNKey(line[i..])) return line[i..]; 407 if (validNkey(line[i..])) return line[i..];
461 break; 408 break;
462 } 409 }
463 } 410 }
464 } 411 }
465} 412}
466 413
467pub fn parseDecoratedNKey(contents: []const u8) !SeedKeyPair { 414pub fn parseDecoratedNkey(contents: []const u8) !SeedKeyPair {
468 var current_off: usize = 0; 415 var current_off: usize = 0;
469
470 var seed: ?[]const u8 = null; 416 var seed: ?[]const u8 = null;
471 if (findKeySection(contents, &current_off) != null) 417 if (findKeySection(contents, &current_off) != null)
472 seed = findKeySection(contents, &current_off); 418 seed = findKeySection(contents, &current_off);
473 if (seed == null) 419 if (seed == null)
474 seed = findNKey(contents) orelse return error.NoNKeySeedFound; 420 seed = findNkey(contents) orelse return error.NoNkeySeedFound;
475 if (!validNKey(seed.?)) 421 if (!validNkey(seed.?))
476 return error.NoNKeySeedFound; 422 return error.NoNkeySeedFound;
477 return fromSeed(seed.?[0..text_seed_len]); 423 return SeedKeyPair.fromTextSeed(seed.?[0..text_seed_len]);
478} 424}
479 425
480pub fn parseDecoratedUserNKey(contents: []const u8) !SeedKeyPair { 426pub fn parseDecoratedUserNkey(contents: []const u8) !SeedKeyPair {
481 var key = try parseDecoratedNKey(contents); 427 var key = try parseDecoratedNkey(contents);
482 if (!mem.startsWith(u8, &key.seed, "SU")) return error.NoNKeyUserSeedFound; 428 if (!mem.startsWith(u8, &key.seed, "SU")) return error.NoNkeyUserSeedFound;
483 defer key.wipe(); 429 defer key.wipe();
484 return key; 430 return key;
485} 431}
486 432
487test { 433test {
488 testing.refAllDecls(@This()); 434 testing.refAllDecls(@This());
489 testing.refAllDecls(Key);
490 testing.refAllDecls(SeedKeyPair); 435 testing.refAllDecls(SeedKeyPair);
491 testing.refAllDecls(PublicKey); 436 testing.refAllDecls(PublicKey);
492} 437}
493 438
494test { 439test {
495 var key_pair = try SeedKeyPair.init(PublicPrefixByte.server); 440 var key_pair = try SeedKeyPair.generate(PublicPrefixByte.server);
496 defer key_pair.wipe(); 441 defer key_pair.wipe();
497 442
498 var decoded_seed = try decodeSeed(&key_pair.seed); 443 var decoded_seed = try decodeSeed(&key_pair.seed);
444 defer decoded_seed.wipe();
499 var encoded_second_time = try encodeSeed(decoded_seed.prefix, &decoded_seed.seed); 445 var encoded_second_time = try encodeSeed(decoded_seed.prefix, &decoded_seed.seed);
446 defer wipeBytes(&encoded_second_time);
500 try testing.expectEqualSlices(u8, &key_pair.seed, &encoded_second_time); 447 try testing.expectEqualSlices(u8, &key_pair.seed, &encoded_second_time);
501 try testing.expect(isValidEncoding(&key_pair.seed)); 448 try testing.expect(isValidEncoding(&key_pair.seed));
502 449
503 var pub_key_str_a = try key_pair.publicKey(); 450 var pub_key_str_a = try key_pair.publicKey();
451 defer wipeBytes(&pub_key_str_a);
504 var priv_key_str = try key_pair.privateKey(); 452 var priv_key_str = try key_pair.privateKey();
453 defer wipeBytes(&priv_key_str);
505 try testing.expect(pub_key_str_a.len != 0); 454 try testing.expect(pub_key_str_a.len != 0);
506 try testing.expect(priv_key_str.len != 0); 455 try testing.expect(priv_key_str.len != 0);
507 try testing.expect(isValidEncoding(&pub_key_str_a)); 456 try testing.expect(isValidEncoding(&pub_key_str_a));
508 try testing.expect(isValidEncoding(&priv_key_str)); 457 try testing.expect(isValidEncoding(&priv_key_str));
509 wipeBytes(&priv_key_str);
510 458
511 var pub_key = try key_pair.intoPublicKey(); 459 var pub_key = try key_pair.intoPublicKey();
460 defer pub_key.wipe();
512 var pub_key_str_b = try pub_key.publicKey(); 461 var pub_key_str_b = try pub_key.publicKey();
462 defer wipeBytes(&pub_key_str_b);
513 try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b); 463 try testing.expectEqualSlices(u8, &pub_key_str_a, &pub_key_str_b);
514} 464}
515 465
516test { 466test {
517 var creds_bytes = try std.fs.cwd().readFileAlloc(testing.allocator, "fixtures/test.creds", std.math.maxInt(usize)); 467 var creds_bytes = try std.fs.cwd().readFileAlloc(testing.allocator, "fixtures/test.creds", std.math.maxInt(usize));
518 defer testing.allocator.free(creds_bytes); 468 defer testing.allocator.free(creds_bytes);
469 defer wipeBytes(creds_bytes);
519 470
520 // TODO(rutgerbrf): validate the contents of the results of these functions 471 // TODO(rutgerbrf): validate the contents of the results of these functions
521 _ = try parseDecoratedUserNKey(creds_bytes); 472 _ = try parseDecoratedUserNkey(creds_bytes);
522 _ = try parseDecoratedJwt(creds_bytes); 473 _ = try parseDecoratedJwt(creds_bytes);
523} 474}
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
162 162
163 try PrefixKeyGenerator.init(arena, ty.?, capitalized_prefix).generate(); 163 try PrefixKeyGenerator.init(arena, ty.?, capitalized_prefix).generate();
164 } else { 164 } else {
165 var kp = nkeys.SeedKeyPair.init(ty.?) catch |e| fatal("could not generate key pair: {e}", .{e}); 165 var kp = nkeys.SeedKeyPair.generate(ty.?) catch |e| fatal("could not generate key pair: {e}", .{e});
166 defer kp.wipe(); 166 defer kp.wipe();
167 try stdout.writeAll(&kp.seed); 167 try stdout.writeAll(&kp.seed);
168 try stdout.writeAll("\n"); 168 try stdout.writeAll("\n");
@@ -396,7 +396,7 @@ const PrefixKeyGenerator = struct {
396 while (true) { 396 while (true) {
397 if (self.done.load(.SeqCst)) return; 397 if (self.done.load(.SeqCst)) return;
398 398
399 var kp = nkeys.SeedKeyPair.init(self.ty) catch |e| fatal("could not generate key pair: {e}", .{e}); 399 var kp = nkeys.SeedKeyPair.generate(self.ty) catch |e| fatal("could not generate key pair: {e}", .{e});
400 defer kp.wipe(); 400 defer kp.wipe();
401 var public_key = kp.publicKey() catch |e| fatal("could not generate public key: {e}", .{e}); 401 var public_key = kp.publicKey() catch |e| fatal("could not generate public key: {e}", .{e});
402 if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue; 402 if (!mem.startsWith(u8, public_key[1..], self.prefix)) continue;
@@ -425,22 +425,6 @@ const PrefixKeyGenerator = struct {
425 }; 425 };
426}; 426};
427 427
428pub fn readKeyFile(allocator: *Allocator, file: fs.File) nkeys.Key {
429 var bytes = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch fatal("could not read key file", .{});
430
431 var iterator = mem.split(bytes, "\n");
432 while (iterator.next()) |line| {
433 if (nkeys.isValidEncoding(line) and line.len == nkeys.text_seed_len) {
434 var k = nkeys.fromText(line) catch continue;
435 defer k.wipe();
436 allocator.free(bytes);
437 return k;
438 }
439 }
440
441 fatal("could not find a valid key", .{});
442}
443
444fn two(slice: []const bool) bool { 428fn two(slice: []const bool) bool {
445 var one = false; 429 var one = false;
446 for (slice) |x| if (x and one) { 430 for (slice) |x| if (x and one) {
@@ -457,6 +441,79 @@ fn toUpper(allocator: *Allocator, slice: []const u8) ![]u8 {
457 return result; 441 return result;
458} 442}
459 443
444pub const Nkey = union(enum) {
445 seed_key_pair: nkeys.SeedKeyPair,
446 public_key: nkeys.PublicKey,
447
448 const Self = @This();
449
450 pub fn publicKey(self: *const Self) !nkeys.text_public {
451 return switch (self.*) {
452 .seed_key_pair => |*kp| try kp.publicKey(),
453 .public_key => |*pk| try pk.publicKey(),
454 };
455 }
456
457 pub fn intoPublicKey(self: *const Self) !nkeys.PublicKey {
458 return switch (self.*) {
459 .seed_key_pair => |*kp| try kp.intoPublicKey(),
460 .public_key => |pk| pk,
461 };
462 }
463
464 pub fn verify(
465 self: *const Self,
466 msg: []const u8,
467 sig: [std.crypto.sign.Ed25519.signature_length]u8,
468 ) !void {
469 return switch (self.*) {
470 .seed_key_pair => |*kp| try kp.verify(msg, sig),
471 .public_key => |*pk| try pk.verify(msg, sig),
472 };
473 }
474
475 pub fn wipe(self: *Self) void {
476 return switch (self.*) {
477 .seed_key_pair => |*kp| kp.wipe(),
478 .public_key => |*pk| pk.wipe(),
479 };
480 }
481
482 pub fn fromText(text: []const u8) !Self {
483 if (!nkeys.isValidEncoding(text)) return error.InvalidEncoding;
484 switch (text[0]) {
485 'S' => {
486 // It's a seed.
487 if (text.len != nkeys.text_seed_len) return error.InvalidSeed;
488 return Self{ .seed_key_pair = try nkeys.SeedKeyPair.fromTextSeed(text[0..nkeys.text_seed_len]) };
489 },
490 'P' => return error.InvalidEncoding, // unsupported for now
491 else => {
492 if (text.len != nkeys.text_public_len) return error.InvalidEncoding;
493 return Self{ .public_key = try nkeys.PublicKey.fromTextPublicKey(text[0..nkeys.text_public_len]) };
494 },
495 }
496 }
497};
498
499pub fn readKeyFile(allocator: *Allocator, file: fs.File) Nkey {
500 var bytes = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch fatal("could not read key file", .{});
501
502 var iterator = mem.split(bytes, "\n");
503 while (iterator.next()) |line| {
504 if (nkeys.isValidEncoding(line) and line.len == nkeys.text_seed_len) {
505 var k = Nkey.fromText(line) catch continue;
506 defer k.wipe();
507 allocator.free(bytes);
508 return k;
509 }
510 }
511
512 fatal("could not find a valid key", .{});
513}
514
460test { 515test {
461 testing.refAllDecls(@This()); 516 testing.refAllDecls(@This());
517 testing.refAllDecls(Nkey);
518 testing.refAllDecls(PrefixKeyGenerator);
462} 519}