diff options
author | Rutger Broekhoff | 2021-05-22 21:19:53 +0200 |
---|---|---|
committer | Rutger Broekhoff | 2021-05-22 21:19:53 +0200 |
commit | 07ac8dc00e7676d7845a881970c259679b0c5d23 (patch) | |
tree | b2ea799f35eab953d083472ea435ca101e7f321f /src | |
parent | 4716921ce42b5153b5c60edf3638e1592f72f743 (diff) | |
download | zig-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.
Diffstat (limited to 'src')
-rw-r--r-- | src/base32.zig | 14 | ||||
-rw-r--r-- | src/nkeys.zig | 189 | ||||
-rw-r--r-- | 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 { | |||
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 | |||
18 | pub 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 | |||
34 | pub 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 | ||
73 | pub const KeyTypePrefixByte = enum(u8) { | 18 | pub 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 | ||
255 | pub fn encodeSeed(prefix: PublicPrefixByte, src: *const [Ed25519.seed_length]u8) !text_seed { | 213 | pub 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 | ||
263 | pub fn decodePrivate(text: *const text_private) ![Ed25519.secret_length]u8 { | 221 | pub 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 | ||
271 | pub fn decodePublic(prefix: PublicPrefixByte, text: *const text_public) ![Ed25519.public_length]u8 { | 229 | pub 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 | ||
278 | fn DecodedNKey(comptime prefix_len: usize, comptime data_len: usize) type { | 237 | fn 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 | ||
315 | pub fn decodeSeed(text: *const text_seed) !DecodedSeed { | 281 | pub 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 | ||
331 | pub 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 | |||
341 | pub fn fromSeed(text: *const text_seed) !SeedKeyPair { | ||
342 | var res = try decodeSeed(text); | ||
343 | wipeBytes(&res.seed); | ||
344 | return SeedKeyPair{ .seed = text.* }; | ||
345 | } | ||
346 | |||
347 | pub fn isValidEncoding(text: []const u8) bool { | 297 | pub 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 | ||
368 | pub fn isValidSeed(text: *const text_seed) bool { | 318 | pub 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 | ||
374 | pub fn isValidPublicKey(text: *const text_public, with_type: ?PublicPrefixByte) bool { | 324 | pub 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 | ||
380 | pub fn fromRawSeed(prefix: PublicPrefixByte, raw_seed: *const [Ed25519.seed_length]u8) !SeedKeyPair { | ||
381 | return SeedKeyPair{ .seed = try encodeSeed(prefix, raw_seed) }; | ||
382 | } | ||
383 | |||
384 | pub fn getNextLine(text: []const u8, off: *usize) ?[]const u8 { | 331 | pub 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, ¤t_off) orelse return contents; | 389 | return findKeySection(contents, ¤t_off) orelse return contents; |
443 | } | 390 | } |
444 | 391 | ||
445 | fn validNKey(text: []const u8) bool { | 392 | fn 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 | ||
454 | fn findNKey(text: []const u8) ?[]const u8 { | 401 | fn 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, ¤t_off) orelse return null; | 404 | var line = getNextLine(text, ¤t_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 | ||
467 | pub fn parseDecoratedNKey(contents: []const u8) !SeedKeyPair { | 414 | pub 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, ¤t_off) != null) | 417 | if (findKeySection(contents, ¤t_off) != null) |
472 | seed = findKeySection(contents, ¤t_off); | 418 | seed = findKeySection(contents, ¤t_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 | ||
480 | pub fn parseDecoratedUserNKey(contents: []const u8) !SeedKeyPair { | 426 | pub 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 | ||
487 | test { | 433 | test { |
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 | ||
494 | test { | 439 | test { |
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 | ||
516 | test { | 466 | test { |
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 | ||
428 | pub 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 | |||
444 | fn two(slice: []const bool) bool { | 428 | fn 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 | ||
444 | pub 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 | |||
499 | pub 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 | |||
460 | test { | 515 | test { |
461 | testing.refAllDecls(@This()); | 516 | testing.refAllDecls(@This()); |
517 | testing.refAllDecls(Nkey); | ||
518 | testing.refAllDecls(PrefixKeyGenerator); | ||
462 | } | 519 | } |