diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/crc16.zig | 4 | ||||
-rw-r--r-- | src/nkeys.zig | 172 | ||||
-rw-r--r-- | src/znk.zig | 19 |
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 @@ | |||
1 | const Error = error{InvalidChecksum}; | 1 | pub const InvalidChecksumError = error{InvalidChecksum}; |
2 | 2 | ||
3 | const crc16tab: [256]u16 = tab: { | 3 | const 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. |
39 | pub fn validate(data: []const u8, expected: u16) !void { | 39 | pub 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; | |||
7 | const mem = std.mem; | 7 | const mem = std.mem; |
8 | const testing = std.testing; | 8 | const testing = std.testing; |
9 | 9 | ||
10 | const Error = error{ | 10 | pub const InvalidPrefixByteError = error{InvalidPrefixByte}; |
11 | InvalidPrefixByte, | 11 | pub const InvalidEncodingError = error{InvalidEncoding}; |
12 | InvalidEncoding, | 12 | pub const InvalidSeedError = error{InvalidSeed}; |
13 | InvalidSeed, | 13 | pub const NoNkeySeedFoundError = error{NoNkeySeedFound}; |
14 | NoNkeySeedFound, | 14 | pub const NoNkeyUserSeedFoundError = error{NoNkeyUserSeedFound}; |
15 | NoNkeyUserSeedFound, | ||
16 | }; | ||
17 | 15 | ||
18 | pub const KeyTypePrefixByte = enum(u8) { | 16 | pub 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; | |||
177 | pub const text_public = [text_public_len]u8; | 180 | pub const text_public = [text_public_len]u8; |
178 | pub const text_seed = [text_seed_len]u8; | 181 | pub const text_seed = [text_seed_len]u8; |
179 | 182 | ||
180 | pub fn encodePublic(prefix: PublicPrefixByte, key: *const [Ed25519.public_length]u8) !text_public { | 183 | fn 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 | ||
184 | pub fn encodePrivate(key: *const [Ed25519.secret_length]u8) !text_private { | 187 | fn 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 | ||
213 | pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) !text_seed { | 216 | pub 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 | ||
221 | pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 { | 224 | pub 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 | |||
229 | pub 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 | ||
237 | fn DecodedNkey(comptime prefix_len: usize, comptime data_len: usize) type { | 226 | fn 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 | ||
281 | pub fn decodeSeed(text: *const text_seed) !DecodedSeed { | 270 | pub const SeedDecodeError = DecodeError || InvalidSeedError; |
271 | |||
272 | pub 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 | ||
331 | pub 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. |
342 | pub fn isKeySectionBarrier(line: []const u8) bool { | 323 | pub 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 | ||
346 | pub fn areKeySectionContentsValid(contents: []const u8) bool { | 327 | const 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 | |||
337 | pub 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 | ||
363 | pub fn findKeySection(text: []const u8, off: *usize) ?[]const u8 { | 342 | pub 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 | ||
387 | pub fn parseDecoratedJwt(contents: []const u8) ![]const u8 { | 363 | pub 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, ¤t_off) orelse return contents; | 365 | return findKeySection(contents, &line_it) orelse return contents; |
390 | } | 366 | } |
391 | 367 | ||
392 | fn validNkey(text: []const u8) bool { | 368 | fn validNkey(text: []const u8) bool { |
@@ -399,9 +375,9 @@ fn validNkey(text: []const u8) bool { | |||
399 | } | 375 | } |
400 | 376 | ||
401 | fn findNkey(text: []const u8) ?[]const u8 { | 377 | fn 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, ¤t_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 | ||
414 | pub fn parseDecoratedNkey(contents: []const u8) !SeedKeyPair { | 391 | pub 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, ¤t_off) != null) | 395 | if (findKeySection(contents, &line_it) != null) |
418 | seed = findKeySection(contents, ¤t_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 | ||
426 | pub fn parseDecoratedUserNkey(contents: []const u8) !SeedKeyPair { | 404 | pub 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 | ||
439 | test { | 417 | test { |
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 | ||
484 | pub fn readKeyFile(allocator: *Allocator, file: fs.File) Nkey { | 484 | pub 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 | ||
500 | test { | 503 | test { |